Facebook Pixel

1658. Minimum Operations to Reduce X to Zero

Problem Description

You have an integer array nums and an integer x. Your goal is to reduce x to exactly 0 by performing a series of operations.

In each operation, you can:

  • Remove either the leftmost element or the rightmost element from the array nums
  • Subtract the value of the removed element from x
  • The array is modified after each removal (it becomes smaller)

You need to find the minimum number of operations required to make x equal to exactly 0. If it's impossible to reduce x to exactly 0, return -1.

For example, if nums = [1, 1, 4, 2, 3] and x = 5, you could:

  • Remove the rightmost element (3), making x = 5 - 3 = 2
  • Remove the rightmost element (2), making x = 2 - 2 = 0 This takes 2 operations.

The key insight in the solution is to reframe the problem: instead of finding elements to remove from the ends, we find the longest contiguous subarray in the middle that we can keep. If we remove elements with sum x from the ends, then the remaining middle subarray has sum sum(nums) - x. By finding the longest subarray with this target sum, we minimize the number of removed elements.

The solution uses a hash table to track prefix sums and their indices. For each position, it checks if there's a previous prefix sum that would create a subarray with the target sum s = sum(nums) - x. The length of the longest such subarray is tracked, and the final answer is the total array length minus this maximum length.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The first observation is that we can only remove elements from the two ends of the array. This might initially suggest a two-pointer or sliding window approach from both ends, but that could lead to complex decision-making about which end to choose at each step.

Let's think about this differently. If we remove some elements from the left and some from the right with a total sum of x, what remains is a contiguous subarray in the middle. This middle subarray must have a sum equal to sum(nums) - x.

This reframing is powerful: instead of finding which elements to remove (which is harder), we find which elements to keep. We want to keep the longest possible contiguous subarray that has sum sum(nums) - x, because keeping more elements means removing fewer elements.

For example, if nums = [1, 1, 4, 2, 3] with total sum 11 and x = 5, we need to find the longest subarray with sum 11 - 5 = 6. The subarray [1, 1, 4] has sum 6 and length 3, so we keep 3 elements and remove 5 - 3 = 2 elements.

To find the longest subarray with a specific sum, we use the classic prefix sum technique with a hash table. As we traverse the array and calculate running prefix sums, we store each prefix sum with its index. For any current prefix sum t, if there exists a previous prefix sum t - s (where s is our target sum), then the subarray between these two positions has sum exactly s.

The hash table allows us to check in O(1) time whether we've seen a prefix sum that would create our target subarray sum. We track the maximum length of all such subarrays found, and the minimum operations needed is n - max_length.

If no valid subarray is found (meaning it's impossible to remove elements summing to exactly x), we return -1.

Learn more about Binary Search, Prefix Sum and Sliding Window patterns.

Solution Approach

Let's walk through the implementation step by step:

  1. Transform the problem: Calculate the target sum s = sum(nums) - x. This represents the sum of the subarray we want to keep in the middle.

  2. Initialize data structures:

    • Create a hash table vis to store prefix sums and their indices. Initialize it with {0: -1} to handle subarrays starting from index 0.
    • Set mx = -1 to track the maximum length of valid subarrays found.
    • Set t = 0 to maintain the running prefix sum.
  3. Traverse the array: For each element at index i with value v:

    • Update the prefix sum: t += v
    • If this prefix sum t hasn't been seen before, record it in the hash table: vis[t] = i
    • Check if t - s exists in the hash table. If it does, it means we've found a subarray with sum s:
      • The subarray spans from index vis[t - s] + 1 to index i
      • The length is i - vis[t - s]
      • Update mx if this length is greater: mx = max(mx, i - vis[t - s])
  4. Return the result:

    • If mx == -1, no valid subarray was found, return -1
    • Otherwise, return len(nums) - mx (the minimum number of elements to remove)

Why this works: When we find t - s in our hash table, it means:

  • At some earlier index j = vis[t - s], the prefix sum was t - s
  • At current index i, the prefix sum is t
  • The sum of elements from index j + 1 to i is t - (t - s) = s

The hash table ensures O(1) lookup time for each check, making the overall time complexity O(n) where n is the length of the array. The space complexity is also O(n) for storing the prefix sums in the hash table.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example with nums = [3, 2, 20, 1, 1, 3] and x = 10.

Step 1: Transform the problem

  • Total sum = 3 + 2 + 20 + 1 + 1 + 3 = 30
  • Target sum s = 30 - 10 = 20 (sum of subarray to keep)

Step 2: Initialize

  • vis = {0: -1} (handles subarrays starting from index 0)
  • mx = -1 (maximum subarray length found)
  • t = 0 (running prefix sum)

Step 3: Traverse the array

IndexValuePrefix Sum tt - sFound in vis?Subarray FoundLengthUpdate
0333-20=-17No--vis[3]=0
1255-20=-15No--vis[5]=1
2202525-20=5Yes!From index 2 to 22-1=1mx=1, vis[25]=2
312626-20=6No--vis[26]=3
412727-20=7No--vis[27]=4
533030-20=10No--vis[30]=5

Detailed look at index 2:

  • Current prefix sum: t = 25
  • Looking for: t - s = 25 - 20 = 5
  • Found 5 in vis at index 1
  • This means subarray from index 1+1=2 to index 2 has sum 20
  • Subarray is [20] with length 2 - 1 = 1
  • Update mx = max(-1, 1) = 1

Step 4: Calculate result

  • Maximum subarray length keeping sum 20: mx = 1
  • Minimum operations: 6 - 1 = 5

Verification: We keep the subarray [20] (sum = 20) in the middle. We remove:

  • From left: [3, 2] (sum = 5)
  • From right: [1, 1, 3] (sum = 5)
  • Total removed: 5 + 5 = 10 = x
  • Number of operations: 5 elements removed

The answer is 5.

Solution Implementation

1class Solution:
2    def minOperations(self, nums: List[int], x: int) -> int:
3        # Calculate target sum for the middle subarray
4        # We want to find the longest subarray with sum = total - x
5        target_sum = sum(nums) - x
6      
7        # Dictionary to store prefix sum and its earliest index
8        # Initialize with 0 sum at index -1 (before array starts)
9        prefix_sum_index = {0: -1}
10      
11        # Track maximum length of valid subarray and current prefix sum
12        max_length = -1
13        current_sum = 0
14      
15        # Iterate through array to find longest subarray with target_sum
16        for index, value in enumerate(nums):
17            # Update current prefix sum
18            current_sum += value
19          
20            # Store first occurrence of this prefix sum
21            if current_sum not in prefix_sum_index:
22                prefix_sum_index[current_sum] = index
23          
24            # Check if we can form a subarray with target_sum
25            # We need prefix_sum where: current_sum - prefix_sum = target_sum
26            required_prefix = current_sum - target_sum
27            if required_prefix in prefix_sum_index:
28                # Calculate length of valid subarray
29                subarray_length = index - prefix_sum_index[required_prefix]
30                max_length = max(max_length, subarray_length)
31      
32        # If no valid subarray found, return -1
33        # Otherwise, return number of elements to remove from both ends
34        return -1 if max_length == -1 else len(nums) - max_length
35
1class Solution {
2    public int minOperations(int[] nums, int x) {
3        // Calculate the target sum for the middle subarray
4        // We need to find the longest subarray with sum = totalSum - x
5        int targetSum = -x;
6        for (int num : nums) {
7            targetSum += num;
8        }
9      
10        // Map to store prefix sum and its first occurrence index
11        Map<Integer, Integer> prefixSumToIndex = new HashMap<>();
12        prefixSumToIndex.put(0, -1); // Base case: empty prefix has sum 0 at index -1
13      
14        // Variables to track the maximum length of valid subarray
15        int maxLength = -1;
16        int currentPrefixSum = 0;
17        int n = nums.length;
18      
19        // Iterate through the array to find the longest subarray with target sum
20        for (int i = 0; i < n; i++) {
21            currentPrefixSum += nums[i];
22          
23            // Store the first occurrence of this prefix sum
24            prefixSumToIndex.putIfAbsent(currentPrefixSum, i);
25          
26            // Check if we can form a subarray with the target sum
27            // currentPrefixSum - (currentPrefixSum - targetSum) = targetSum
28            if (prefixSumToIndex.containsKey(currentPrefixSum - targetSum)) {
29                int subarrayLength = i - prefixSumToIndex.get(currentPrefixSum - targetSum);
30                maxLength = Math.max(maxLength, subarrayLength);
31            }
32        }
33      
34        // If no valid subarray found, return -1
35        // Otherwise, return the minimum operations (elements to remove from both ends)
36        return maxLength == -1 ? -1 : n - maxLength;
37    }
38}
39
1class Solution {
2public:
3    int minOperations(vector<int>& nums, int x) {
4        // Calculate the target sum for the middle subarray
5        // We want to find the longest subarray with sum = totalSum - x
6        int totalSum = accumulate(nums.begin(), nums.end(), 0);
7        int targetSum = totalSum - x;
8      
9        // Hash map to store the first occurrence of each prefix sum
10        // Key: prefix sum, Value: index where this sum first occurred
11        unordered_map<int, int> prefixSumIndex = {{0, -1}};
12      
13        // Track the maximum length of subarray with target sum
14        int maxLength = -1;
15        int currentPrefixSum = 0;
16        int n = nums.size();
17      
18        // Iterate through the array to find the longest subarray with target sum
19        for (int i = 0; i < n; ++i) {
20            // Update current prefix sum
21            currentPrefixSum += nums[i];
22          
23            // Store the first occurrence of this prefix sum
24            if (prefixSumIndex.find(currentPrefixSum) == prefixSumIndex.end()) {
25                prefixSumIndex[currentPrefixSum] = i;
26            }
27          
28            // Check if there exists a subarray ending at index i with sum = targetSum
29            // This happens when (currentPrefixSum - targetSum) exists in our map
30            if (prefixSumIndex.find(currentPrefixSum - targetSum) != prefixSumIndex.end()) {
31                // Update the maximum length if we found a longer subarray
32                maxLength = max(maxLength, i - prefixSumIndex[currentPrefixSum - targetSum]);
33            }
34        }
35      
36        // If no valid subarray found, return -1
37        // Otherwise, return the minimum operations (elements from both ends)
38        return maxLength == -1 ? -1 : n - maxLength;
39    }
40};
41
1/**
2 * Finds the minimum number of operations to reduce x to exactly 0 by removing elements from either end of the array
3 * @param nums - The input array of positive integers
4 * @param x - The target value to reduce to 0
5 * @returns The minimum number of operations, or -1 if it's impossible
6 */
7function minOperations(nums: number[], x: number): number {
8    // Calculate the target sum for the middle subarray
9    // We need to find the longest subarray with sum = totalSum - x
10    const targetSum: number = nums.reduce((acc: number, cur: number) => acc + cur, -x);
11  
12    // Map to store the first occurrence index of each prefix sum
13    const prefixSumToIndex: Map<number, number> = new Map<number, number>([[0, -1]]);
14  
15    // Initialize variables for tracking the maximum subarray length and current prefix sum
16    let maxSubarrayLength: number = -1;
17    let currentPrefixSum: number = 0;
18    const arrayLength: number = nums.length;
19  
20    // Iterate through the array to find the longest subarray with the target sum
21    for (let i = 0; i < arrayLength; ++i) {
22        currentPrefixSum += nums[i];
23      
24        // Store the first occurrence of this prefix sum
25        if (!prefixSumToIndex.has(currentPrefixSum)) {
26            prefixSumToIndex.set(currentPrefixSum, i);
27        }
28      
29        // Check if we can form a subarray with the target sum ending at index i
30        if (prefixSumToIndex.has(currentPrefixSum - targetSum)) {
31            const subarrayLength: number = i - prefixSumToIndex.get(currentPrefixSum - targetSum)!;
32            maxSubarrayLength = Math.max(maxSubarrayLength, subarrayLength);
33        }
34    }
35  
36    // If we found a valid subarray, return the number of elements to remove from both ends
37    // Otherwise, return -1
38    return maxSubarrayLength !== -1 ? arrayLength - maxSubarrayLength : -1;
39}
40

Time and Space Complexity

Time Complexity: O(n)

The algorithm iterates through the array nums exactly once using a single for loop with enumerate(nums). Within each iteration, all operations are constant time:

  • Dictionary lookup (t not in vis) - O(1) average case
  • Dictionary insertion (vis[t] = i) - O(1) average case
  • Dictionary lookup (t - s in vis) - O(1) average case
  • Max comparison and arithmetic operations - O(1)

Since we perform O(1) operations for each of the n elements, the overall time complexity is O(n).

Space Complexity: O(n)

The space usage comes from:

  • Dictionary vis which stores prefix sums as keys and their indices as values. In the worst case, all prefix sums are unique, requiring storage for up to n+1 entries (including the initial {0: -1})
  • Variables s, mx, t, i, v which use constant space O(1)

The dominant factor is the dictionary storage, making the overall space complexity O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Handling Edge Case When x Equals Total Sum

A critical pitfall occurs when x equals the sum of all elements in the array. In this case, target_sum = sum(nums) - x = 0, meaning we need to find the longest subarray with sum 0. However, if we want to remove ALL elements (making the "kept" subarray empty), the algorithm might not handle this correctly.

Problem Example:

nums = [1, 2, 3]
x = 6  # sum(nums) = 6
# target_sum = 0
# We should remove all elements, returning 3

Issue: The algorithm finds the longest subarray with sum 0, but an empty array also has sum 0. Without proper initialization, we might miss this valid solution.

Solution: The initialization prefix_sum_index = {0: -1} actually handles this case correctly. When current_sum = target_sum, we get required_prefix = 0, which exists at index -1, giving us the correct subarray length.

2. Overwriting Prefix Sum Indices

Another pitfall is updating the prefix sum index when the same sum appears multiple times. This would lose the earliest occurrence and potentially miss the longest valid subarray.

Problem Example:

nums = [1, -1, 1, 2]
x = 1
# Prefix sums: [1, 0, 1, 3]
# If we update index for sum=1 from 0 to 2, we lose potential longer subarrays

Solution: Always check if the prefix sum already exists before storing:

if current_sum not in prefix_sum_index:
    prefix_sum_index[current_sum] = index

3. Integer Overflow in Other Languages

While Python handles arbitrary precision integers automatically, implementing this in languages like Java or C++ could cause overflow when calculating the sum of the array or prefix sums.

Solution for Other Languages:

  • Use long or long long data types
  • Consider using modular arithmetic if values are extremely large
  • Add overflow detection logic

4. Misunderstanding When No Solution Exists

The algorithm returns -1 when max_length == -1, which happens when no subarray with target_sum is found. However, developers might incorrectly assume this means x > sum(nums).

Clarification: No solution exists in these cases:

  • x > sum(nums) (not enough elements to sum to x)
  • x < 0 (all array elements are non-negative in typical problems)
  • No combination of elements from ends sums to exactly x

The beauty of the prefix sum approach is that it automatically handles all these cases by simply not finding a valid subarray with the required sum.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More