Divide an Array Into Subarrays With Minimum Cost II
HardPrompt
You are given a 0-indexed array of integers nums of length n, and two positive integers k and dist.
The cost of an array is the value of its first element. For example, the cost of [1,2,3] is 1 while the cost of [3,4,1] is 3.
You need to divide nums into k disjoint contiguous subarrays, such that the difference between the starting index of the second subarray and the starting index of the kth subarray should be less than or equal to dist. In other words, if you divide nums into the subarrays nums[0..(i1 - 1)], nums[i1..(i2 - 1)], ..., nums[ik-1..(n - 1)], then ik-1 - i1 <= dist.
Return the minimum possible sum of the cost of these subarrays.
Example 1:
Input: nums = [1,3,2,6,4,2], k = 3, dist = 3
Output: 5
Explanation: The best possible way to divide nums into 3 subarrays is: [1,3], [2,6,4], and [2]. This choice is valid because ik-1 - i1 is 5 - 2 = 3 which is equal to dist. The total cost is nums[0] + nums[2] + nums[5] which is 1 + 2 + 2 = 5.
It can be shown that there is no possible way to divide nums into 3 subarrays at a cost lower than 5.Example 2:
Input: nums = [10,1,2,2,2,1], k = 4, dist = 3
Output: 15
Explanation: The best possible way to divide nums into 4 subarrays is: [10], [1], [2], and [2,2,1]. This choice is valid because ik-1 - i1 is 3 - 1 = 2 which is less than dist. The total cost is nums[0] + nums[1] + nums[2] + nums[3] which is 10 + 1 + 2 + 2 = 15.
The division [10], [1], [2,2,2], and [1] is not valid, because the difference between ik-1 and i1 is 5 - 1 = 4, which is greater than dist.
It can be shown that there is no possible way to divide nums into 4 subarrays at a cost lower than 15.Example 3:
Input: nums = [10,8,18,9], k = 3, dist = 1
Output: 36
Explanation: The best possible way to divide nums into 4 subarrays is: [10], [8], and [18,9]. This choice is valid because ik-1 - i1 is 2 - 1 = 1 which is equal to dist.The total cost is nums[0] + nums[1] + nums[2] which is 10 + 8 + 18 = 36.
The division [10], [8,18], and [9] is not valid, because the difference between ik-1 and i1 is 3 - 1 = 2, which is greater than dist.
It can be shown that there is no possible way to divide nums into 3 subarrays at a cost lower than 36.
Constraints:
3 <= n <= 1051 <= nums[i] <= 1093 <= k <= nk - 2 <= dist <= n - 2
Approaches
2 approaches with complexity analysis and trade-offs.
The core of the problem is to select k-1 starting indices for the second to k-th subarrays from nums[1:]. The constraint i_{k-1} - i_1 <= dist implies that all these k-1 chosen indices must lie within a contiguous block of dist + 1 indices. To minimize the cost, we need to find a block (or window) of dist + 1 indices in [1, n-1] such that the sum of the k-1 smallest nums values within that block is minimized. This brute-force approach iterates through every possible window of size dist + 1, and for each window, it explicitly finds the k-1 smallest elements by sorting, calculating their sum, and tracking the minimum sum found across all windows.
Algorithm
- The cost of the first subarray is always
nums[0], so we add it at the end. We need to find the minimum sum of costs for the otherk-1subarrays. - Let
m = k - 1. We need to choosemindices fromnums[1:]. - The chosen indices must lie in a window of size
dist + 1. - Iterate through all possible start indices
ifor a window of sizedist + 1. The window of indices is[i, i + dist].iranges from1ton - 1 - dist. - For each window:
- Collect all
nums[j]forjfromitoi + distinto a list. - Sort the list.
- Calculate the sum of the first
melements. - Update a global minimum sum with this sum if it's smaller.
- Collect all
- Return
nums[0]plus the global minimum sum.
Walkthrough
The algorithm iterates through all possible starting positions for a window of size dist + 1. The windows slide over the part of the array from which we can choose our k-1 start indices, which is nums[1] to nums[n-1].
Let m = k - 1. For each window, we do the following:
- Extract the numbers within the current window into a temporary list.
- Sort this list in non-decreasing order.
- Sum the first
melements of the sorted list. This gives the minimum cost for thek-1subarrays if their start indices are chosen from this window. - We keep track of the minimum sum found among all windows.
Finally, the total minimum cost is nums[0] (the cost of the first subarray, which is fixed) plus the minimum sum we found.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public long minimumCost(int[] nums, int k, int dist) { int n = nums.length; long minOtherCosts = Long.MAX_VALUE; int m = k - 1; // The window of candidate indices for the other k-1 subarrays // slides from [1, 1+dist] to [n-1-dist, n-1]. // The starting index of the window 'i' can go from 1 to n-1-dist. for (int i = 1; i <= n - 1 - dist; i++) { List<Integer> windowNums = new ArrayList<>(); for (int j = i; j <= i + dist; j++) { windowNums.add(nums[j]); } Collections.sort(windowNums); long currentSum = 0; for (int l = 0; l < m; l++) { currentSum += windowNums.get(l); } minOtherCosts = Math.min(minOtherCosts, currentSum); } return (long)nums[0] + minOtherCosts; }}Complexity
Time
O((n - dist) * dist * log(dist)). The outer loop runs about `n - dist` times. Inside the loop, we create a list of size `dist + 1` and sort it, which takes `O(dist * log(dist))` time. This is too slow for `n` and `dist` up to 10<sup>5</sup>.
Space
O(dist) to store the numbers in the current window for sorting.
Trade-offs
Pros
Conceptually simple and easy to implement.
Correctly models the problem by checking all valid possibilities.
Cons
Highly inefficient due to repeated work.
Sorting within each sliding window step leads to a high time complexity, making it too slow for the given constraints.
This approach significantly optimizes the previous method by avoiding redundant computations. Instead of re-sorting the entire window every time it slides, we use a more sophisticated data structure to maintain the k-1 smallest elements efficiently. As the window slides one position to the right, one element is removed, and one is added. We can use two balanced binary search trees (implemented as TreeMap in Java to handle duplicates) to keep track of the elements in the window. One TreeMap (smallMap) will store the k-1 smallest elements, and the other (largeMap) will store the rest. This allows us to find the sum of the k-1 smallest elements in O(log(dist)) time per slide, leading to a much faster overall solution.
Algorithm
- Let
m = k - 1. We need to find the minimum sum ofmelements chosen from a sliding window of sizedist + 1overnums[1:]. - Use two
TreeMaps:smallMapto store themsmallest elements andlargeMapfor the rest. - Maintain
smallSum, the sum of elements insmallMap. - Initialize the window with elements from
nums[1]tonums[1 + dist]. For each element, add it to the two-map structure and keep the maps balanced. - After initialization,
smallSumis the cost for the first window. Initializemin_cost_sumwith this value. - Iterate from
i = 2ton - 1 - distto slide the window:- Remove the element
nums[i-1]from the data structure. - Add the new element
nums[i+dist]to the data structure. - Both
addandremoveoperations must maintain the properties of the two maps and updatesmallSum. - Update
min_cost_sum = min(min_cost_sum, smallSum).
- Remove the element
- Return
nums[0] + min_cost_sum.
Walkthrough
We use a sliding window of size dist + 1 over nums[1:]. To efficiently find the sum of the k-1 smallest elements in this window, we use two TreeMaps: smallMap and largeMap.
smallMap: Stores thek-1smallest elements from the current window. We also maintainsmallSum, the sum of elements insmallMap, andsmallSize, the count of elements.largeMap: Stores the remaining elements in the window.
The algorithm proceeds as follows:
- Initialize the data structure with the elements from the first window,
nums[1...1+dist]. We defineaddandremovehelper methods that update the two maps andsmallSumwhile keeping them balanced. An element is added or removed, and then elements are moved between the maps to ensuresmallMapcontains exactly thek-1smallest elements. - The initial
smallSumis our first candidate for the minimum sum. - We then slide the window one element at a time from left to right. In each step, we
removethe element that's leaving the window andaddthe element that's entering. - After each slide,
smallSumholds the sum of thek-1smallest elements in the new window. We update our overall minimum sum with the currentsmallSum. - The final answer is
nums[0]plus this overall minimum sum.
This method reduces the complexity of each step of the sliding window from O(dist * log(dist)) to O(log(dist)), making it efficient enough to pass the given constraints.
import java.util.TreeMap; class Solution { private TreeMap<Integer, Integer> smallMap; private TreeMap<Integer, Integer> largeMap; private long smallSum; private int smallSize; private int m; // Represents k - 1 public long minimumCost(int[] nums, int k, int dist) { this.m = k - 1; this.smallMap = new TreeMap<>(); this.largeMap = new TreeMap<>(); this.smallSum = 0; this.smallSize = 0; for (int i = 1; i <= 1 + dist; i++) { add(nums[i]); } long minOtherCosts = smallSum; for (int i = 2; i <= nums.length - 1 - dist; i++) { remove(nums[i - 1]); add(nums[i + dist]); minOtherCosts = Math.min(minOtherCosts, smallSum); } return (long)nums[0] + minOtherCosts; } private void add(int num) { addToMap(smallMap, num); smallSum += num; smallSize++; balance(); } private void remove(int num) { if (largeMap.containsKey(num)) { removeFromMap(largeMap, num); } else { removeFromMap(smallMap, num); smallSum -= num; smallSize--; } balance(); } private void balance() { while (smallSize > m) { int maxInSmall = smallMap.lastKey(); removeFromMap(smallMap, maxInSmall); smallSum -= maxInSmall; smallSize--; addToMap(largeMap, maxInSmall); } while (smallSize < m && !largeMap.isEmpty()) { int minInLarge = largeMap.firstKey(); removeFromMap(largeMap, minInLarge); addToMap(smallMap, minInLarge); smallSum += minInLarge; smallSize++; } } private void addToMap(TreeMap<Integer, Integer> map, int num) { map.put(num, map.getOrDefault(num, 0) + 1); } private void removeFromMap(TreeMap<Integer, Integer> map, int num) { map.put(num, map.get(num) - 1); if (map.get(num) == 0) { map.remove(num); } }}Complexity
Time
O(n log(dist)). The main loop iterates `O(n)` times. Each step involves an `add` and a `remove` operation on the `TreeMap`s. The maps store at most `dist + 1` distinct elements, so each map operation takes `O(log(dist))` time.
Space
O(dist) to store the elements of the window in the two `TreeMap`s.
Trade-offs
Pros
Highly efficient with a time complexity suitable for the given constraints.
Effectively solves the problem by reducing re-computation in the sliding window.
Cons
Implementation is more complex and requires careful handling of the two-map data structure, including edge cases and balancing logic.
The logic for adding and removing elements while maintaining the two-map invariant can be tricky to get right.
Solutions
Solution
class Solution {public long minimumCost(int[] nums, int k, int dist) { long result = Long.MAX_VALUE, sum = 0L; int n = nums.length; TreeSet<Integer> set1 = new TreeSet<>((a, b)->nums[a] == nums[b] ? a - b : nums[a] - nums[b]); TreeSet<Integer> set2 = new TreeSet<>((a, b)->nums[a] == nums[b] ? a - b : nums[a] - nums[b]); for (int i = 1; i < n; i++) { set1.add(i); sum += nums[i]; if (set1.size() >= k) { int x = set1.pollLast(); sum -= nums[x]; set2.add(x); } if (i - dist > 0) { result = Math.min(result, sum); int temp = i - dist; if (set1.contains(temp)) { set1.remove(temp); sum -= nums[temp]; if (set2.size() > 0) { int y = set2.pollFirst(); sum += nums[y]; set1.add(y); } } else { set2.remove(i - dist); } } } return result + nums[0]; }}class Solution {public: long long minimumCost(vector<int> &nums, int k, int dist) { multiset<int> sml, big; int sz = dist + 1; long long sum = 0, ans = 0; for (int i = 1; i <= sz; i++) { sml.insert(nums[i]); sum += nums[i]; } while (sml.size() > k - 1) { big.insert(*sml.rbegin()); sum -= *sml.rbegin(); sml.erase(sml.find(*sml.rbegin())); } ans = sum; for (int i = sz + 1; i < nums.size(); i++) { sum += nums[i]; sml.insert(nums[i]); if (big.find(nums[i - sz]) != big.end()) { big.erase(big.find(nums[i - sz])); } else { sum -= nums[i - sz]; sml.erase(sml.find(nums[i - sz])); } while (sml.size() > k - 1) { sum -= *sml.rbegin(); big.insert(*sml.rbegin()); sml.erase(sml.find(*sml.rbegin())); } while (sml.size() < k - 1) { sum += *big.begin(); sml.insert(*big.begin()); big.erase(big.begin()); } while (!sml.empty() && !big.empty() && *sml.rbegin() > *big.begin()) { sum -= *sml.rbegin() - *big.begin(); sml.insert(*big.begin()); big.insert(*sml.rbegin()); sml.erase(sml.find(*sml.rbegin())); big.erase(big.begin()); } ans = min(ans, sum); } int p = 0; return nums[0] + ans; }};from sortedcontainers import SortedList class Solution : def minimumCost ( self , nums : List [ int ], k : int , dist : int ) -> int : n = len ( nums ) sl = SortedList () y = nums [ 0 ] ans = float ( "inf" ) i = 1 running_sum = 0 for j in range ( 1 , n ): pos = bisect . bisect_left ( sl , nums [ j ]) sl . add ( nums [ j ]) if pos < k - 1 : running_sum += nums [ j ] if len ( sl ) > k - 1 : running_sum -= sl [ k - 1 ] while j - i > dist : removed_pos = sl . index ( nums [ i ]) removed_element = nums [ i ] sl . remove ( removed_element ) if removed_pos < k - 1 : running_sum -= removed_element if len ( sl ) >= k - 1 : running_sum += sl [ k - 2 ] i += 1 if j - i + 1 >= k - 1 : ans = min ( ans , running_sum ) return ans + yVideo 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.