Minimum Operations to Make Elements Within K Subarrays Equal
HardPrompt
You are given an integer array nums and two integers, x and k. You can perform the following operation any number of times (including zero):
- Increase or decrease any element of
numsby 1.
Return the minimum number of operations needed to have at least k non-overlapping subarrays of size exactly x in nums, where all elements within each subarray are equal.
Example 1:
Input: nums = [5,-2,1,3,7,3,6,4,-1], x = 3, k = 2
Output: 8
Explanation:
- Use 3 operations to add 3 to
nums[1]and use 2 operations to subtract 2 fromnums[3]. The resulting array is[5, 1, 1, 1, 7, 3, 6, 4, -1]. - Use 1 operation to add 1 to
nums[5]and use 2 operations to subtract 2 fromnums[6]. The resulting array is[5, 1, 1, 1, 7, 4, 4, 4, -1]. - Now, all elements within each subarray
[1, 1, 1](from indices 1 to 3) and[4, 4, 4](from indices 5 to 7) are equal. Since 8 total operations were used, 8 is the output.
Example 2:
Input: nums = [9,-2,-2,-2,1,5], x = 2, k = 2
Output: 3
Explanation:
- Use 3 operations to subtract 3 from
nums[4]. The resulting array is[9, -2, -2, -2, -2, 5]. - Now, all elements within each subarray
[-2, -2](from indices 1 to 2) and[-2, -2](from indices 3 to 4) are equal. Since 3 operations were used, 3 is the output.
Constraints:
2 <= nums.length <= 105-106 <= nums[i] <= 1062 <= x <= nums.length1 <= k <= 152 <= k * x <= nums.length
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves two main stages. First, it pre-calculates the minimum operations (cost) required to make each possible subarray of size x have all equal elements. This is done naively by sorting each subarray to find its median and then summing the absolute differences. Second, it uses dynamic programming to determine the minimum total cost to select k non-overlapping subarrays from the calculated costs.
Algorithm
- Step 1: Pre-calculate Subarray Costs (Naively)
- Create a
costsarray of sizen - x + 1. - Iterate from
i = 0ton - x. - For each
i, extract the subarraynums[i...i+x-1]. - Sort the subarray to find its median (element at index
(x-1)/2). - Calculate the sum of absolute differences of each element from the median. This is
costs[i].
- Create a
- Step 2: Dynamic Programming
- Create a 2D DP table
dpof size(n - x + 2) x (k + 1), initialized to a large value. - Set the base case:
dp[i][0] = 0for alli(cost of 0 subarrays is 0). - Iterate
jfrom1tok(number of subarrays). - Iterate
ifrom1ton - x + 1(number of possible starting positions). - Calculate
dp[i][j]using the recurrence:dp[i][j] = min(dp[i-1][j], costs[i-1] + dp[i-x][j-1]).dp[i-1][j]represents not selecting the subarray starting ati-1.costs[i-1] + dp[i-x][j-1]represents selecting the subarray ati-1. This is only possible ifi-x >= 0.
- The final answer is
dp[n-x+1][k].
- Create a 2D DP table
Walkthrough
Detailed Steps:
-
Pre-calculate Subarray Costs: We create a
costsarray to store the cost for each potential subarray. For every starting indexifrom0ton-x, we consider the subarraynums[i...i+x-1]. The minimum operations to make all elements in this subarray equal is to change them all to the subarray's median. We find the median by sorting a copy of the subarray. The cost is the sum of absolute differences between each element and the median. This process is repeated for alln-x+1possible subarrays.private long calculateCost(int[] nums, int start, int x) { int[] sub = new int[x]; System.arraycopy(nums, start, sub, 0, x); Arrays.sort(sub); long median = sub[(x - 1) / 2]; long cost = 0; for (int val : sub) { cost += Math.abs(val - median); } return cost;} -
Dynamic Programming: After computing all individual subarray costs, the problem reduces to selecting
knon-overlapping entries from thecostsarray with minimum sum. Letdp[i][j]be the minimum cost to selectjnon-overlapping subarrays from the firstipossible starting positions (costs[0...i-1]).The recurrence relation is:
dp[i][j] = min(dp[i-1][j], costs[i-1] + dp[i-x][j-1])dp[i-1][j]: This corresponds to the case where we do not select the subarray starting ati-1.costs[i-1] + dp[i-x][j-1]: This corresponds to selecting the subarray starting ati-1. If we do, the previousj-1subarrays must be chosen from starting positions up toi-1-xto ensure they are non-overlapping.
The final answer is the value in
dp[n-x+1][k].// Part of the main functionint m = n - x + 1;long[] costs = new long[m];for (int i = 0; i < m; i++) { costs[i] = calculateCost(nums, i, x);} long[][] dp = new long[m + 1][k + 1];long INF = Long.MAX_VALUE / 2;for (long[] row : dp) { Arrays.fill(row, INF);}for (int i = 0; i <= m; i++) { dp[i][0] = 0;} for (int j = 1; j <= k; j++) { for (int i = 1; i <= m; i++) { // Option 1: Don't take subarray starting at i-1 long option1 = dp[i - 1][j]; // Option 2: Take subarray starting at i-1 long option2 = INF; if (i - x >= 0) { if (dp[i - x][j - 1] != INF) { option2 = costs[i - 1] + dp[i - x][j - 1]; } } else { // This can only be the first subarray chosen if (j == 1) { option2 = costs[i - 1]; } } dp[i][j] = Math.min(option1, option2); }}return dp[m][k];
Complexity
Time
O(n * x * log(x) + n * k) - The cost calculation step dominates. There are `O(n)` windows, and for each, we sort `x` elements, taking `O(x log x)`. The DP part takes `O(n*k)`.
Space
O(n*k) - We need `O(n)` space for the `costs` array and `O(n*k)` for the DP table.
Trade-offs
Pros
The logic is straightforward and easy to understand.
It correctly separates the problem into two distinct subproblems: cost calculation and optimal selection.
Cons
The naive cost calculation is very slow, with a time complexity of
O(n * x * log(x)), which will result in a 'Time Limit Exceeded' verdict for largex.
This approach significantly optimizes the bottleneck of the previous method. Instead of naively recalculating the cost for each subarray, it uses a sliding window approach with two heaps to find the median and cost in O(log x) time per slide. The dynamic programming part to select the k best subarrays remains the same but now operates on the efficiently pre-computed costs.
Algorithm
- Step 1: Pre-calculate Subarray Costs (Efficiently)
- Create a
costsarray of sizen - x + 1. - Use a sliding window of size
xovernums. - Maintain two heaps (a max-heap for the smaller half, a min-heap for the larger half) to track elements in the window.
- For the first window, populate the heaps and calculate
costs[0]. - For subsequent windows, slide the window by removing the old element and adding the new one. Update the heaps in
O(log x)time. - Calculate the cost for each window using the heaps' state and store it in the
costsarray.
- Create a
- Step 2: Dynamic Programming
- This step is identical to the previous approach. Use the efficiently calculated
costsarray to fill theO(n*k)DP table. - The recurrence
dp[i][j] = min(dp[i-1][j], costs[i-1] + dp[i-x][j-1])is used. - The final answer is
dp[n-x+1][k].
- This step is identical to the previous approach. Use the efficiently calculated
Walkthrough
Detailed Steps:
-
Efficient Cost Calculation with Sliding Window Median: The main improvement is in calculating the
costsarray. We slide a window of sizexacross thenumsarray. To find the median of the elements in the window at each step efficiently, we use two heaps:- A max-heap (
small) to store the smaller half of the window's elements. - A min-heap (
large) to store the larger half.
We keep the heaps balanced so that their sizes differ by at most 1. The median is always
large.peek(). We also maintain the sum of elements in each heap to calculate the cost inO(1). When the window slides, we remove one element and add another. These heap operations takeO(log x)time. This reduces the total time for cost pre-calculation toO(n log x).// Note: In Java, PriorityQueue.remove(Object) is O(N). A true O(log N) removal// requires a more complex implementation (e.g., with a HashMap for lazy removal)// or using a balanced binary search tree (TreeMap).// The following snippet illustrates the idea but has O(x) removal time.private long[] calculateAllCosts(int[] nums, int x) { int n = nums.length; int m = n - x + 1; long[] costs = new long[m]; // Two heaps for sliding window median PriorityQueue<Integer> small = new PriorityQueue<>(Collections.reverseOrder()); PriorityQueue<Integer> large = new PriorityQueue<>(); long smallSum = 0, largeSum = 0; // Helper to balance heaps Runnable balance = () -> { while (small.size() > large.size()) { int val = small.poll(); smallSum -= val; large.add(val); largeSum += val; } while (large.size() > small.size() + 1) { int val = large.poll(); largeSum -= val; small.add(val); smallSum += val; } }; // Initial window for (int i = 0; i < x; i++) { small.add(nums[i]); smallSum += nums[i]; } balance.run(); // Sliding the window for (int i = 0; i < m; i++) { // Calculate cost for current window long median = large.peek(); costs[i] = (median * small.size() - smallSum) + (largeSum - median * large.size()); // Slide to next window if (i + x < n) { int toRemove = nums[i]; int toAdd = nums[i + x]; if (small.remove(toRemove)) smallSum -= toRemove; else if (large.remove(toRemove)) largeSum -= toRemove; if (!large.isEmpty() && toAdd >= large.peek()) { large.add(toAdd); largeSum += toAdd; } else { small.add(toAdd); smallSum += toAdd; } balance.run(); } } return costs;} - A max-heap (
-
Dynamic Programming: This part is identical to Approach 1. We use the
costsarray generated above and fill thedp[m+1][k+1]table to find the minimum total cost. The time complexity for this part remainsO(n*k).
Complexity
Time
O(n * log(x) + n * k) - `O(n log x)` for calculating all costs with the sliding window median and `O(n*k)` for the DP.
Space
O(n*k) - `O(x)` for the heaps, `O(n)` for the `costs` array, and `O(n*k)` for the DP table.
Trade-offs
Pros
Highly efficient time complexity that passes the given constraints.
Effectively tackles the performance bottleneck of the naive approach.
Cons
Requires a more complex data structure (two heaps) for the sliding window median.
The DP table still consumes a large amount of memory,
O(n*k).
This is the most optimal solution, building upon the previous approach by optimizing its space complexity. It uses the same efficient O(n log x) sliding window median technique for cost calculation. However, it improves the dynamic programming stage by reducing the space from O(n*k) to O(n). This is achieved by noticing that to compute the costs for j subarrays, we only need the results for j-1 subarrays, not all j-2, j-3, ....
Algorithm
- Step 1: Pre-calculate Subarray Costs (Efficiently)
- This step is identical to Approach 2. Use the sliding window with two heaps to compute the
costsarray inO(n log x)time.
- This step is identical to Approach 2. Use the sliding window with two heaps to compute the
- Step 2: Space-Optimized Dynamic Programming
- Observe that
dp[i][j]only depends ondpvalues from columnjandj-1. - Instead of a 2D DP table, use two 1D arrays:
dp_prev(forj-1subarrays) anddp_curr(forjsubarrays), each of sizen-x+2. - Iterate
jfrom1tok. - In each iteration, compute
dp_currusing values fromdp_prevand the already computed values ofdp_curr.dp_curr[i] = min(dp_curr[i-1], costs[i-1] + dp_prev[i-x]). - After the inner loop over
icompletes,dp_currbecomes the newdp_prevfor the nextj. - The final answer is the last element of the DP array after
kiterations.
- Observe that
Walkthrough
Detailed Steps:
-
Efficient Cost Calculation: This step is identical to Approach 2. We generate the
costsarray inO(n log x)time using the sliding window median technique. -
Space-Optimized Dynamic Programming: The key insight is that the DP state
dp[i][j]only depends ondp[i-1][j]anddp[i-x][j-1]. This means we only need to store the DP results for the current number of subarrays (j) and the previous one (j-1).We use two 1D arrays,
dp_prevanddp_curr, of sizem+1(wherem = n-x+1).dp_prevstores the minimum costs for selectingj-1subarrays.dp_curris used to compute the minimum costs for selectingjsubarrays.
The outer loop runs from
j = 1tok. In each iteration, we computedp_currusingdp_prev. At the end of the iteration, the content ofdp_curris copied todp_prevto be used in the next iteration.public long minOperations(int[] nums, int x, int k) { int n = nums.length; int m = n - x + 1; // Assume costs array is calculated efficiently as in Approach 2 long[] costs = calculateAllCosts(nums, x); long[] dpPrev = new long[m + 1]; // for j-1 subarrays long[] dpCurr = new long[m + 1]; // for j subarrays long INF = Long.MAX_VALUE / 2; // dpPrev is implicitly all 0s, for the base case j=0. for (int j = 1; j <= k; j++) { Arrays.fill(dpCurr, INF); for (int i = 1; i <= m; i++) { // Option 1: Don't take subarray starting at i-1 long option1 = dpCurr[i - 1]; // Option 2: Take subarray starting at i-1 long option2 = INF; if (i - x >= 0) { if (dpPrev[i - x] != INF) { option2 = costs[i - 1] + dpPrev[i - x]; } } else { // This can only be the first subarray chosen if (j == 1) { option2 = costs[i - 1]; } } dpCurr[i] = Math.min(option1, option2); } // Prepare for next iteration: current becomes previous System.arraycopy(dpCurr, 0, dpPrev, 0, m + 1); } return dpPrev[m];}// calculateAllCosts function from Approach 2 would be needed here.
Complexity
Time
O(n * log(x) + n * k) - The time complexity is the same as the non-space-optimized version, as the logic remains identical.
Space
O(n + x) - `O(x)` for heaps, `O(n)` for the `costs` array, and `O(n)` for the two DP arrays.
Trade-offs
Pros
Optimal time complexity for the given constraints.
Optimal space complexity, using
O(n)space instead ofO(n*k).
Cons
The implementation is the most complex of the three approaches.
The logic for space-optimized DP requires careful state management.
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.