Minimum Cost to Split an Array
HardPrompt
You are given an integer array nums and an integer k.
Split the array into some number of non-empty subarrays. The cost of a split is the sum of the importance value of each subarray in the split.
Let trimmed(subarray) be the version of the subarray where all numbers which appear only once are removed.
- For example,
trimmed([3,1,2,4,3,4]) = [3,4,3,4].
The importance value of a subarray is k + trimmed(subarray).length.
- For example, if a subarray is
[1,2,3,3,3,4,4], then trimmed([1,2,3,3,3,4,4]) = [3,3,3,4,4].The importance value of this subarray will bek + 5.
Return the minimum possible cost of a split of nums.
A subarray is a contiguous non-empty sequence of elements within an array.
Example 1:
Input: nums = [1,2,1,2,1,3,3], k = 2
Output: 8
Explanation: We split nums to have two subarrays: [1,2], [1,2,1,3,3].
The importance value of [1,2] is 2 + (0) = 2.
The importance value of [1,2,1,3,3] is 2 + (2 + 2) = 6.
The cost of the split is 2 + 6 = 8. It can be shown that this is the minimum possible cost among all the possible splits.Example 2:
Input: nums = [1,2,1,2,1], k = 2
Output: 6
Explanation: We split nums to have two subarrays: [1,2], [1,2,1].
The importance value of [1,2] is 2 + (0) = 2.
The importance value of [1,2,1] is 2 + (2) = 4.
The cost of the split is 2 + 4 = 6. It can be shown that this is the minimum possible cost among all the possible splits.Example 3:
Input: nums = [1,2,1,2,1], k = 5
Output: 10
Explanation: We split nums to have one subarray: [1,2,1,2,1].
The importance value of [1,2,1,2,1] is 5 + (3 + 2) = 10.
The cost of the split is 10. It can be shown that this is the minimum possible cost among all the possible splits.
Constraints:
1 <= nums.length <= 10000 <= nums[i] < nums.length1 <= k <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a straightforward dynamic programming formulation. We define dp[i] as the minimum cost to split the prefix of the array nums of length i. To compute dp[i], we consider all possible previous split points j < i. The cost would be the minimum cost to split the array up to j (dp[j]) plus the cost of the newly formed last subarray nums[j...i-1]. The main drawback is the repeated and inefficient calculation of the cost for each subarray.
Algorithm
- Create a
dparray of sizen+1, wherenis the length ofnums.dp[i]will store the minimum cost to split the prefixnums[0...i-1]. - Initialize
dp[0] = 0and all otherdp[i]to a very large value. - Iterate
ifrom1ton:- For each
i, iteratejfrom0toi-1. Thisjrepresents a potential split point, makingnums[j...i-1]the last subarray. - For each subarray
nums[j...i-1], calculate its cost:- Create a frequency map (or an array since element values are bounded) for the elements in
nums[j...i-1]by iterating fromjtoi-1. - Calculate
trimmed_lengthby iterating through the frequency map and summing up the counts of elements that appear more than once. - The cost of the subarray is
k + trimmed_length.
- Create a frequency map (or an array since element values are bounded) for the elements in
- Update the
dpstate:dp[i] = min(dp[i], dp[j] + cost).
- For each
- The final answer is
dp[n].
Walkthrough
The recurrence relation for this DP approach is dp[i] = min_{0 <= j < i} (dp[j] + cost(nums[j...i-1])). The cost of a subarray nums[j...i-1] is k + trimmed(nums[j...i-1]).length.
To implement this, we use two nested loops to iterate through all possible start (j) and end (i) points of the subarrays. For each subarray, we perform another loop to calculate the frequency of its elements to determine the trimmed_length. This three-level nested loop structure leads to a cubic time complexity.
import java.util.Arrays;import java.util.HashMap;import java.util.Map; class Solution { public int minCost(int[] nums, int k) { int n = nums.length; long[] dp = new long[n + 1]; Arrays.fill(dp, Long.MAX_VALUE); dp[0] = 0; for (int i = 1; i <= n; i++) { for (int j = 0; j < i; j++) { // Subarray is nums[j...i-1] // Since 0 <= nums[i] < nums.length, an array can be used instead of a HashMap int[] counts = new int[n]; for (int l = j; l < i; l++) { counts[nums[l]]++; } int trimmedLength = 0; // This loop can be optimized, but overall complexity remains O(N^3) for (int count : counts) { if (count > 1) { trimmedLength += count; } } long cost = k + trimmedLength; if (dp[j] != Long.MAX_VALUE) { dp[i] = Math.min(dp[i], dp[j] + cost); } } } return (int) dp[n]; }}Complexity
Time
O(N^3), where N is the number of elements in `nums`. There are two nested loops for `i` and `j` (O(N^2)), and inside, calculating the cost of a subarray takes O(N) time.
Space
O(N), where N is the number of elements in `nums`. This is for the `dp` array and the frequency count array used inside the loops.
Trade-offs
Pros
It is a direct translation of the problem's recurrence relation, making it relatively easy to understand and implement.
Cons
The time complexity of O(N^3) is too slow for the given constraints (N <= 1000), and this solution will likely result in a 'Time Limit Exceeded' error.
This approach refines the naive DP solution by optimizing the calculation of subarray costs. The core DP state and recurrence remain the same, but we avoid recomputing the trimmed_length from scratch for every subarray. By iterating backwards for the start of the last subarray, we can incrementally update the trimmed_length in constant time for each new element added. This optimization reduces the overall time complexity from cubic to quadratic.
Algorithm
- Use the same DP state
dp[i]as the naive approach, representing the minimum cost for the prefixnums[0...i-1]. - Initialize
dp[0] = 0and otherdpentries to a large value. - Iterate
ifrom1ton:- To calculate
dp[i], we will consider all subarraysnums[j...i-1]ending ati-1. - Instead of a forward
jloop, we loopjbackwards fromi-1down to0. - Maintain a frequency array
countsand a runningtrimmedLengthfor the current subarray being considered. - In the inner loop (for
j):- Add
nums[j]to the current subarray (which wasnums[j+1...i-1]). - Update the frequency of
nums[j]incounts. - Update
trimmedLengthbased on the new count ofnums[j]. If the count becomes 2,trimmedLengthincreases by 2. If it's greater than 2, it increases by 1. - Calculate the cost
k + trimmedLengthand updatedp[i] = min(dp[i], dp[j] + cost).
- Add
- To calculate
- Return
dp[n].
Walkthrough
The key insight is that for a fixed endpoint i, as we consider different start points j, the subarrays nums[j...i-1] are related. Specifically, nums[j...i-1] is just nums[j+1...i-1] with nums[j] prepended.
We can exploit this. For a fixed i, we iterate j from i-1 down to 0. We maintain the trimmed_length of the subarray nums[j...i-1]. When we move from j to j-1, we are essentially adding nums[j-1] to our subarray. The change in trimmed_length depends only on the number of times nums[j-1] has already appeared in nums[j...i-1]. This update can be done in O(1) time with a frequency map. This eliminates the innermost O(N) loop from the naive approach.
import java.util.Arrays; class Solution { public int minCost(int[] nums, int k) { int n = nums.length; long[] dp = new long[n + 1]; Arrays.fill(dp, Long.MAX_VALUE); dp[0] = 0; for (int i = 1; i <= n; i++) { int[] counts = new int[n]; // Since 0 <= nums[i] < nums.length int trimmedLength = 0; for (int j = i - 1; j >= 0; j--) { int num = nums[j]; counts[num]++; if (counts[num] == 2) { trimmedLength += 2; } else if (counts[num] > 2) { trimmedLength += 1; } long cost = k + trimmedLength; if (dp[j] != Long.MAX_VALUE) { dp[i] = Math.min(dp[i], dp[j] + cost); } } } return (int) dp[n]; }}Note: We use a long array for dp because intermediate costs dp[j] + cost can exceed Integer.MAX_VALUE given the constraints on k, even though the final answer is guaranteed to fit in an int.
Complexity
Time
O(N^2), where N is the number of elements in `nums`. The two nested loops dominate the runtime, and the operations inside are O(1).
Space
O(N), for the `dp` array and the `counts` frequency array.
Trade-offs
Pros
The O(N^2) complexity is efficient enough to solve the problem within typical time limits for N up to 1000.
It's a common and powerful optimization technique for this class of DP problems.
Cons
While efficient enough for the given constraints, an O(N^2) solution might still be too slow for significantly larger inputs.
Solutions
Solution
class Solution {private Integer[] f;private int[] nums;private int n, k;public int minCost(int[] nums, int k) { n = nums.length; this.k = k; this.nums = nums; f = new Integer[n]; return dfs(0); }private int dfs(int i) { if (i >= n) { return 0; } if (f[i] != null) { return f[i]; } int[] cnt = new int[n]; int one = 0; int ans = 1 << 30; for (int j = i; j < n; ++j) { int x = ++cnt[nums[j]]; if (x == 1) { ++one; } else if (x == 2) { --one; } ans = Math.min(ans, k + j - i + 1 - one + dfs(j + 1)); } return f[i] = ans; }}class Solution {public: int minCost(vector<int> &nums, int k) { int n = nums.size(); int f[n]; memset(f, 0, sizeof f); function<int(int)> dfs = [&](int i) { if (i >= n) { return 0; } if (f[i]) { return f[i]; } int cnt[n]; memset(cnt, 0, sizeof cnt); int one = 0; int ans = 1 << 30; for (int j = i; j < n; ++j) { int x = ++cnt[nums[j]]; if (x == 1) { ++one; } else if (x == 2) { --one; } ans = min(ans, k + j - i + 1 - one + dfs(j + 1)); } return f[i] = ans; }; return dfs(0); }};class Solution: def minCost(self, nums: List[int], k: int) -> int: @ cache def dfs(i): if i >= n: return 0 cnt = Counter() one = 0 ans = inf for j in range(i, n): cnt[nums[j]] += 1 if cnt[nums[j]] == 1: one += 1 elif cnt[nums[j]] == 2: one -= 1 ans = min(ans, k + j - i + 1 - one + dfs(j + 1)) return ans n = len(nums) return dfs(0)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.