Minimum Difference in Sums After Removal of Elements
HardPrompt
You are given a 0-indexed integer array nums consisting of 3 * n elements.
You are allowed to remove any subsequence of elements of size exactly n from nums. The remaining 2 * n elements will be divided into two equal parts:
- The first
nelements belonging to the first part and their sum issumfirst. - The next
nelements belonging to the second part and their sum issumsecond.
The difference in sums of the two parts is denoted as sumfirst - sumsecond.
- For example, if
sumfirst = 3andsumsecond = 2, their difference is1. - Similarly, if
sumfirst = 2andsumsecond = 3, their difference is-1.
Return the minimum difference possible between the sums of the two parts after the removal of n elements.
Example 1:
Input: nums = [3,1,2]
Output: -1
Explanation: Here, nums has 3 elements, so n = 1.
Thus we have to remove 1 element from nums and divide the array into two equal parts.
- If we remove nums[0] = 3, the array will be [1,2]. The difference in sums of the two parts will be 1 - 2 = -1.
- If we remove nums[1] = 1, the array will be [3,2]. The difference in sums of the two parts will be 3 - 2 = 1.
- If we remove nums[2] = 2, the array will be [3,1]. The difference in sums of the two parts will be 3 - 1 = 2.
The minimum difference between sums of the two parts is min(-1,1,2) = -1. Example 2:
Input: nums = [7,9,5,8,1,3]
Output: 1
Explanation: Here n = 2. So we must remove 2 elements and divide the remaining array into two parts containing two elements each.
If we remove nums[2] = 5 and nums[3] = 8, the resultant array will be [7,9,1,3]. The difference in sums will be (7+9) - (1+3) = 12.
To obtain the minimum difference, we should remove nums[1] = 9 and nums[4] = 1. The resultant array becomes [7,5,8,3]. The difference in sums of the two parts is (7+5) - (8+3) = 1.
It can be shown that it is not possible to obtain a difference smaller than 1.
Constraints:
nums.length == 3 * n1 <= n <= 1051 <= nums[i] <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through all possible ways to partition the original array into a prefix and a suffix, from which the two parts of the final array will be formed. For each partition, it calculates the required sums by sorting the prefix and suffix subarrays.
Algorithm
- Initialize
min_diffto a very large value. - Iterate through all possible split points
ifromnto2*n. - For each
i:- Create a prefix subarray
nums[0...i-1]. - Sort the prefix subarray.
- Calculate
sum_firstby summing the firstn(smallest) elements. - Create a suffix subarray
nums[i...3*n-1]. - Sort the suffix subarray.
- Calculate
sum_secondby summing the lastn(largest) elements. - Update
min_diff = min(min_diff, sum_first - sum_second).
- Create a prefix subarray
- Return
min_diff.
Walkthrough
The core idea is to recognize that the final 2n elements are formed by taking n elements from a prefix nums[0...i-1] and n elements from a suffix nums[i...3n-1] of the original array, preserving relative order. The split point i can range from n to 2n.
To minimize sum_first - sum_second, we must select the n smallest elements for the first part and the n largest elements for the second part.
The algorithm iterates through each possible split index i from n to 2n. In each iteration:
- It takes the prefix
nums[0...i-1]. - It sorts this prefix and sums up the first
nelements to getsum_first. - It takes the suffix
nums[i...3n-1]. - It sorts this suffix and sums up the last
nelements (the largest ones) to getsum_second. - It calculates the difference
sum_first - sum_secondand updates the overall minimum difference.
This process is repeated for all valid split points, and the minimum difference found is the result.
import java.util.Arrays; class Solution { public long minimumDifference(int[] nums) { int totalSize = nums.length; int n = totalSize / 3; long minDiff = Long.MAX_VALUE; for (int i = n; i <= 2 * n; i++) { // Create and sort prefix int[] prefix = Arrays.copyOfRange(nums, 0, i); Arrays.sort(prefix); long sumFirst = 0; for (int j = 0; j < n; j++) { sumFirst += prefix[j]; } // Create and sort suffix int[] suffix = Arrays.copyOfRange(nums, i, totalSize); Arrays.sort(suffix); long sumSecond = 0; for (int j = 0; j < n; j++) { sumSecond += suffix[suffix.length - 1 - j]; } minDiff = Math.min(minDiff, sumFirst - sumSecond); } return minDiff; }}Complexity
Time
O(n^2 * log n). The main loop runs `n+1` times. Inside the loop, sorting arrays of size up to `O(n)` takes `O(n log n)` time, leading to a total complexity of `O(n * (n log n))`.
Space
O(n), for storing the prefix and suffix subarrays in each iteration. The size of these subarrays can be up to `2n`.
Trade-offs
Pros
Conceptually simple and easy to understand.
Directly translates the problem definition into code.
Cons
Highly inefficient due to repeated sorting of overlapping subarrays.
Will result in a 'Time Limit Exceeded' error for the given constraints.
This is an optimized approach that avoids re-computation by pre-calculating the possible values for sum_first and sum_second for all split points. It uses heaps (priority queues) to efficiently maintain the sum of the n smallest/largest elements as we iterate through the array.
Algorithm
- Let
N = nums.lengthandn = N / 3. - Create a
prefix_min_sumarray. Use a max-heap of sizento iterate from left to right, calculating the sum of thensmallest elements innums[0...i]and storing it inprefix_min_sum[i]. - Create a
suffix_max_sumarray. Use a min-heap of sizento iterate from right to left, calculating the sum of thenlargest elements innums[i...N-1]and storing it insuffix_max_sum[i]. - Initialize
min_diffto a very large value. - Iterate
ifromnto2*n:- The first part's sum is
prefix_min_sum[i-1](sum ofnsmallest fromnums[0...i-1]). - The second part's sum is
suffix_max_sum[i](sum ofnlargest fromnums[i...N-1]). - Update
min_diff = min(min_diff, prefix_min_sum[i-1] - suffix_max_sum[i]).
- The first part's sum is
- Return
min_diff.
Walkthrough
The problem can be broken down into finding min(min_sum_prefix(i-1) - max_sum_suffix(i)) for all valid split points i from n to 2n.
min_sum_prefix(k)is the sum of thensmallest elements innums[0...k].max_sum_suffix(k)is the sum of thenlargest elements innums[k...3n-1].
We can compute all necessary min_sum_prefix values in a single forward pass and all max_sum_suffix values in a single backward pass.
Forward Pass (for sum_first):
We iterate from left to right, using a max-heap of size n to keep track of the n smallest elements seen so far. As we iterate to index k, we add nums[k] to the heap. If the heap size exceeds n, we remove the largest element. We maintain a running sum of elements in the heap and store these sums in a prefix_min_sum array.
Backward Pass (for sum_second):
We iterate from right to left, using a min-heap of size n to keep track of the n largest elements seen so far. The logic is similar: as we iterate to index k from the right, we add nums[k] to the heap and remove the smallest if the size exceeds n. We store these sums in a suffix_max_sum array.
Final Calculation:
After pre-computation, we iterate through all possible split points i from n to 2n. For each i, the difference is prefix_min_sum[i-1] - suffix_max_sum[i]. We find the minimum among all these differences.
import java.util.PriorityQueue;import java.util.Collections; class Solution { public long minimumDifference(int[] nums) { int totalSize = nums.length; int n = totalSize / 3; long[] prefix_min_sum = new long[totalSize]; PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); long currentSum = 0; for (int i = 0; i < totalSize; i++) { maxHeap.add(nums[i]); currentSum += nums[i]; if (maxHeap.size() > n) { currentSum -= maxHeap.poll(); } if (maxHeap.size() == n) { prefix_min_sum[i] = currentSum; } } long[] suffix_max_sum = new long[totalSize]; PriorityQueue<Integer> minHeap = new PriorityQueue<>(); currentSum = 0; for (int i = totalSize - 1; i >= 0; i--) { minHeap.add(nums[i]); currentSum += nums[i]; if (minHeap.size() > n) { currentSum -= minHeap.poll(); } if (minHeap.size() == n) { suffix_max_sum[i] = currentSum; } } long minDiff = Long.MAX_VALUE; for (int i = n; i <= 2 * n; i++) { long sumFirst = prefix_min_sum[i - 1]; long sumSecond = suffix_max_sum[i]; minDiff = Math.min(minDiff, sumFirst - sumSecond); } return minDiff; }}Complexity
Time
O(n log n). The forward pass to compute `prefix_min_sum` takes `O(3n * log n)`. The backward pass for `suffix_max_sum` also takes `O(3n * log n)`. The final loop takes `O(n)`. The dominant factor is `O(n log n)`.
Space
O(n). We use two arrays of size `3n` (`prefix_min_sum`, `suffix_max_sum`) and two heaps of size `n`. This all amounts to `O(n)` space.
Trade-offs
Pros
Highly efficient and passes the given constraints.
Effectively uses heaps to avoid redundant calculations, following a dynamic programming-like pattern of pre-computation.
Cons
More complex to implement than the brute-force approach.
Requires careful handling of indices and heap logic.
Solutions
Solution
class Solution {public long minimumDifference(int[] nums) { int m = nums.length; int n = m / 3; long s = 0; long[] pre = new long[m + 1]; PriorityQueue<Integer> pq = new PriorityQueue<>((a, b)->b - a); for (int i = 1; i <= n * 2; ++i) { int x = nums[i - 1]; s += x; pq.offer(x); if (pq.size() > n) { s -= pq.poll(); } pre[i] = s; } s = 0; long[] suf = new long[m + 1]; pq = new PriorityQueue<>(); for (int i = m; i > n; --i) { int x = nums[i - 1]; s += x; pq.offer(x); if (pq.size() > n) { s -= pq.poll(); } suf[i] = s; } long ans = 1L << 60; for (int i = n; i <= n * 2; ++i) { ans = Math.min(ans, pre[i] - suf[i + 1]); } return ans; }}class Solution {public: long long minimumDifference(vector<int> &nums) { int m = nums.size(); int n = m / 3; using ll = long long; ll s = 0; ll pre[m + 1]; priority_queue<int> q1; for (int i = 1; i <= n * 2; ++i) { int x = nums[i - 1]; s += x; q1.push(x); if (q1.size() > n) { s -= q1.top(); q1.pop(); } pre[i] = s; } s = 0; ll suf[m + 1]; priority_queue<int, vector<int>, greater<int>> q2; for (int i = m; i > n; --i) { int x = nums[i - 1]; s += x; q2.push(x); if (q2.size() > n) { s -= q2.top(); q2.pop(); } suf[i] = s; } ll ans = 1e18; for (int i = n; i <= n * 2; ++i) { ans = min(ans, pre[i] - suf[i + 1]); } return ans; }};class Solution: def minimumDifference(self, nums: List[int]) -> int: m = len(nums) n = m // 3 s = 0 pre = [0] * (m + 1) q1 = [] for i, x in enumerate(nums[: n * 2], 1): s += x heappush(q1, - x) if len(q1) > n: s -= - heappop(q1) pre[i] = s s = 0 suf = [0] * (m + 1) q2 = [] for i in range(m, n, - 1): x = nums[i - 1] s += x heappush(q2, x) if len(q2) > n: s -= heappop(q2) suf[i] = s return min(pre[i] - suf[i + 1] for i in range(n, n * 2 + 1))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.