1658. Minimum Operations to Reduce X to Zero - Explanation
Description
In one operation, remove either the first or the last remaining element of nums and subtract its value from x. Return the fewest operations needed to make x exactly zero. If no sequence of these removals works, return -1.
Example 1:
Input: nums = [2,5,1,3]
x = 5
Output: 2Explanation: Remove 2 from the left and 3 from the right. Their sum is 5, so two operations suffice. Neither endpoint alone equals 5.
Example 2:
Input: nums = [2,3]
x = 5
Output: 2Explanation: Remove both elements, whose sum is 2 + 3 = 5. This uses two operations and leaves an empty array.
Example 3:
Input: nums = [4,2,4]
x = 3
Output: -1Explanation: The initial endpoints are both 4. Removing either would overshoot x = 3, so no valid sequence exists.
Constraints:
1 <= nums.length <= 1000001 <= nums[i] <= 100001 <= x <= 1000000000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Prefix Sum - Used to efficiently compute sums of subarrays without recalculating from scratch
- Sliding Window - The optimal solution transforms the problem into finding the longest subarray with a target sum
- Hash Map - Used to store prefix sums for O(1) lookup of complement values
- Binary Search - An alternative approach to find matching prefix sums in sorted prefix arrays
1. Brute Force
Intuition
We can only remove elements from the left or right ends of the array. The total sum we remove must equal x. We try every possible combination: take some elements from the left (prefix) and some from the right (suffix), checking if their combined sum equals x. For each valid combination, we track the min number of elements removed.
Algorithm
- First, check all suffix-only cases by iterating from the right and accumulating a
suffixSum. IfsuffixSum == x, updateres. - Then, for each prefix length (iterating from left), calculate
prefixSum. IfprefixSum == x, updateres. - For each prefix, try combining it with suffixes by iterating from the right. If
prefixSum + suffixSum == x, updatereswith the combined count. - Return
-1if no valid combination is found, otherwise returnres.
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
n = len(nums)
res = n + 1
suffixSum = prefixSum = 0
for i in range(n - 1, -1, -1):
suffixSum += nums[i]
if suffixSum == x:
res = min(res, n - i)
for i in range(n):
prefixSum += nums[i]
suffixSum = 0
if prefixSum == x:
res = min(res, i + 1)
for j in range(n - 1, i, -1):
suffixSum += nums[j]
if prefixSum + suffixSum == x:
res = min(res, i + 1 + n - j)
return -1 if res == n + 1 else resTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
2. Prefix Sum + Binary Search
Intuition
Instead of recalculating prefix sums repeatedly, we precompute them. For each suffixSum we consider, we need to find a prefixSum that equals x - suffixSum. Since prefix sums are sorted in increasing order (all elements are positive), we can use binary search to quickly find if such a prefix exists. This eliminates the inner loop from the brute force approach.
Algorithm
- Build a prefix sum array where
prefixSum[i]represents the sum of the firstielements. - If the total sum is less than
x, return-1immediately. - Use binary search to find if any prefix alone equals
x. - Iterate from the right, building
suffixSum. For eachsuffixSum:- If
suffixSum == x, updateres. - If
suffixSum > x,breakearly. - Binary search in the prefix array for
x - suffixSum, ensuring the prefix and suffix do not overlap.
- If
- Return
-1if no valid combination exists, otherwise returnres.
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
n = len(nums)
prefixSum = [0] * (n + 1)
for i in range(n):
prefixSum[i + 1] = prefixSum[i] + nums[i]
if x > prefixSum[n]:
return -1
def binarySearch(target, m):
l, r = 1, m
index = n + 1
while l <= r:
mid = (l + r) >> 1
if prefixSum[mid] >= target:
if prefixSum[mid] == target:
index = mid
r = mid - 1
else:
l = mid + 1
return index
res = binarySearch(x, n)
suffixSum = 0
for i in range(n - 1, 0, -1):
suffixSum += nums[i]
if suffixSum == x:
res = min(res, n - i)
break
if suffixSum > x: break
res = min(res, binarySearch(x - suffixSum, i) + n - i)
return -1 if res == n + 1 else resTime & Space Complexity
- Time complexity:
- Space complexity:
3. Prefix Sum + Hash Map
Intuition
Here is a clever reframing: removing elements from both ends that sum to x is the same as keeping a contiguous subarray in the middle that sums to total - x. The problem becomes finding the longest subarray with sum equal to target = total - x. A hash map storing prefix sums lets us check in O(1) whether the required complement exists.
Algorithm
- Calculate
target = total - x. Iftargetis negative, return-1. Iftargetis0, return the array length. - Use a hash map to store each
prefixSumand itsindex. Initialize with{0: -1}to handle subarrays starting atindex0. - Iterate through the array, maintaining a running
prefixSum:- Check if
prefixSum - targetexists in the map. If so, we found a subarray with sumtarget. - Track the
maxlength of such subarrays. - Store the current
prefixSumin the map.
- Check if
- Return
n - maxLengthif a valid subarray was found, otherwise-1.
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
total = sum(nums)
if total == x:
return len(nums)
target = total - x
if target < 0:
return -1
res = -1
prefixSum = 0
prefixMap = {0: -1} # prefixSum -> index
for i, num in enumerate(nums):
prefixSum += num
if prefixSum - target in prefixMap:
res = max(res, i - prefixMap[prefixSum - target])
prefixMap[prefixSum] = i
return len(nums) - res if res != -1 else -1Time & Space Complexity
- Time complexity:
- Space complexity:
4. Sliding Window
Intuition
Building on the same insight as the hash map approach, we want the longest subarray summing to target = total - x. Since all elements are positive, the subarray sum increases as we expand and decreases as we shrink. This monotonic property allows us to use a sliding window: expand the right boundary to include more elements, and shrink from the left when the sum exceeds the target.
Algorithm
- Calculate
target = total - x. This is the sum we want the middle subarray to have. - Use two pointers
landrto define the current window, withcurSumtracking the window sum. - Expand
rto include elements. IfcurSumexceedstarget, shrink fromluntilcurSum <= target. - When
curSum == target, record thewindowlength if it is themaxseen. - The answer is
n - maxWindow. Return-1if no valid window was found.
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
target = sum(nums) - x
cur_sum = 0
max_window = -1
l = 0
for r in range(len(nums)):
cur_sum += nums[r]
while l <= r and cur_sum > target:
cur_sum -= nums[l]
l += 1
if cur_sum == target:
max_window = max(max_window, r - l + 1)
return -1 if max_window == -1 else len(nums) - max_windowTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Not Recognizing the Problem Transformation
The key insight is that removing elements from both ends that sum to x is equivalent to finding the longest contiguous subarray that sums to total - x. Many solutions fail because they try to directly simulate removing from both ends, leading to inefficient or incorrect approaches.
Forgetting to Handle Edge Cases
When total == x, the answer is n (remove all elements). When total < x, the answer is -1 (impossible). When target = total - x is negative, return -1. Missing any of these edge cases causes wrong answers on specific test cases.
Overlapping Prefix and Suffix in Brute Force
In approaches that combine prefix and suffix sums, you must ensure the prefix and suffix do not overlap (i.e., the suffix must start after the prefix ends). A common bug is allowing j <= i instead of j > i, which double-counts elements and produces incorrect results.
Sign in to join the discussion