Facebook Pixel

3354. Make Array Elements Equal to Zero

Problem Description

You have an integer array nums and need to simulate a ball bouncing process.

The process works as follows:

  1. Starting Setup: Choose a starting position curr where nums[curr] == 0 (must be a zero), and pick an initial movement direction (either left or right).

  2. Movement Rules: Once started, follow these rules repeatedly:

    • If curr goes outside the array bounds [0, n - 1], the process stops
    • If nums[curr] == 0, continue moving in the same direction:
      • If moving right: increment curr (move to curr + 1)
      • If moving left: decrement curr (move to curr - 1)
    • If nums[curr] > 0:
      • Decrease nums[curr] by 1
      • Reverse your direction (left becomes right, right becomes left)
      • Take one step in the new direction
  3. Valid Selection: A choice of starting position and initial direction is considered "valid" if, after the process ends, all elements in nums become 0.

Your task is to count how many valid selections exist (combinations of starting positions and initial directions).

Example Understanding:

  • When the moving object hits a non-zero element, it decrements that element and bounces back (reverses direction)
  • The object passes through zeros without changing them
  • The process continues until the object leaves the array bounds
  • A valid selection ensures all non-zero elements get reduced to exactly 0
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about what happens when we start from a zero position and move in one direction. Every time we hit a non-zero element, we decrement it by 1 and bounce back. This means each non-zero element acts like a "wall" that we bounce off of, and each bounce reduces its height by 1.

The key insight is that for all elements to become zero, we need to hit each non-zero element exactly as many times as its value. Since we bounce back and forth, elements on the left side of our starting position will be hit when moving left, and elements on the right side will be hit when moving right.

Consider the total sum s of all elements in the array. If we start at a zero position with left-side sum l and right-side sum s - l:

  • When moving initially to the left, we'll eventually bounce and hit all right elements too
  • When moving initially to the right, we'll eventually bounce and hit all left elements too

For a valid selection, the "bouncing ball" must hit elements on both sides the correct number of times. Due to the alternating nature of bounces:

  • If l == s - l (equal sums on both sides), we can start moving either left or right, giving us 2 valid selections
  • If |l - (s - l)| == 1 (sums differ by exactly 1), we need to start moving toward the side with the larger sum to ensure proper bouncing pattern, giving us 1 valid selection
  • If the difference is greater than 1, no valid selection exists from this position

This explains why the solution iterates through the array, maintains a running sum l of elements to the left, and counts valid selections based on the relationship between l and s - l at each zero position.

Learn more about Prefix Sum patterns.

Solution Approach

The solution uses a prefix sum technique combined with enumeration to efficiently count valid selections.

Algorithm Steps:

  1. Calculate Total Sum: First, compute the total sum s of all elements in the array. This represents the total number of "bounces" needed.

  2. Track Left Sum: Initialize variables ans = 0 (to count valid selections) and l = 0 (to track the sum of elements to the left of current position).

  3. Iterate Through Array: For each element x in nums:

    • If x is non-zero: Add it to the left sum l (accumulating the sum as we move right)
    • If x is zero (a potential starting position):
      • Calculate right sum as s - l
      • Check the relationship between left and right sums:
        • If l * 2 == s (equivalent to l == s - l): Both directions work, add 2 to answer
        • If abs(l * 2 - s) == 1 (equivalent to |l - (s - l)| == 1): Only one direction works, add 1 to answer

Why This Works:

The mathematical transformation l * 2 == s is equivalent to l == s - l, checking if left and right sums are equal.

Similarly, abs(l * 2 - s) == 1 checks if the absolute difference between left sum and right sum equals 1, which can be expanded as:

  • l * 2 - s == 1 means l - (s - l) == 1 (left sum is 1 more than right)
  • l * 2 - s == -1 means l - (s - l) == -1 (right sum is 1 more than left)

Time Complexity: O(n) where n is the length of the array - single pass through the array.

Space Complexity: O(1) - only using a few variables to track sums and count.

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 the solution with nums = [0, 2, 0, 1, 0].

Step 1: Calculate Total Sum

  • Total sum s = 0 + 2 + 0 + 1 + 0 = 3
  • Initialize ans = 0 (count of valid selections) and l = 0 (left sum)

Step 2: Iterate Through Array

Position 0 (nums[0] = 0):

  • This is a zero, so it's a potential starting position
  • Left sum l = 0, right sum = s - l = 3 - 0 = 3
  • Check: l * 2 = 0, s = 3, so l * 2 - s = -3
  • abs(-3) = 3, which is not 1, so no valid selections from here

Position 1 (nums[1] = 2):

  • This is non-zero, add to left sum: l = 0 + 2 = 2
  • Not a starting position, continue

Position 2 (nums[2] = 0):

  • This is a zero, potential starting position
  • Left sum l = 2, right sum = s - l = 3 - 2 = 1
  • Check: l * 2 = 4, s = 3, so l * 2 - s = 1
  • abs(1) = 1, so exactly one direction works! ans = 0 + 1 = 1
  • Since left sum (2) > right sum (1), we must start moving left to hit enough bounces

Position 3 (nums[3] = 1):

  • This is non-zero, add to left sum: l = 2 + 1 = 3
  • Not a starting position, continue

Position 4 (nums[4] = 0):

  • This is a zero, potential starting position
  • Left sum l = 3, right sum = s - l = 3 - 3 = 0
  • Check: l * 2 = 6, s = 3, so l * 2 - s = 3
  • abs(3) = 3, which is not 1, so no valid selections from here

Final Answer: 1

To verify: Starting at position 2 and moving left:

  • From position 2, move left to position 1 (nums[1] = 2)
  • Hit 2, decrement to 1, bounce right
  • Move right through position 2 (zero) to position 3 (nums[3] = 1)
  • Hit 1, decrement to 0, bounce left
  • Move left through position 2 to position 1 (nums[1] = 1)
  • Hit 1, decrement to 0, bounce right
  • Move right through positions 2, 3, 4 and exit the array
  • All elements are now 0 ✓

Solution Implementation

1class Solution:
2    def countValidSelections(self, nums: List[int]) -> int:
3        # Calculate the total sum of all elements in the array
4        total_sum = sum(nums)
5      
6        # Initialize the count of valid selections and left sum
7        valid_count = 0
8        left_sum = 0
9      
10        # Iterate through each element in the array
11        for num in nums:
12            if num != 0:
13                # If current element is non-zero, add it to the left sum
14                left_sum += num
15            else:
16                # At a zero position, check if it's a valid selection point
17              
18                # If left sum equals right sum (perfectly balanced)
19                # Both directions are valid, so add 2
20                if left_sum * 2 == total_sum:
21                    valid_count += 2
22                  
23                # If left and right sums differ by exactly 1
24                # One direction is valid, so add 1
25                elif abs(left_sum * 2 - total_sum) == 1:
26                    valid_count += 1
27      
28        return valid_count
29
1class Solution {
2    public int countValidSelections(int[] nums) {
3        // Calculate the total sum of all elements in the array
4        int totalSum = Arrays.stream(nums).sum();
5      
6        // Initialize result counter and left sum accumulator
7        int validCount = 0;
8        int leftSum = 0;
9      
10        // Iterate through each element in the array
11        for (int currentValue : nums) {
12            if (currentValue != 0) {
13                // Add non-zero values to the left sum
14                leftSum += currentValue;
15            } else {
16                // At a zero position, check balance conditions
17              
18                // Perfect balance: left sum equals right sum
19                // leftSum == totalSum - leftSum => 2 * leftSum == totalSum
20                if (leftSum * 2 == totalSum) {
21                    // Both directions are valid when perfectly balanced
22                    validCount += 2;
23                } 
24                // Near balance: difference between left and right is at most 1
25                // |leftSum - rightSum| <= 1 => |leftSum - (totalSum - leftSum)| <= 1
26                // => |2 * leftSum - totalSum| <= 1
27                else if (Math.abs(leftSum * 2 - totalSum) <= 1) {
28                    // One direction is valid when nearly balanced
29                    validCount++;
30                }
31            }
32        }
33      
34        return validCount;
35    }
36}
37
1class Solution {
2public:
3    int countValidSelections(vector<int>& nums) {
4        // Calculate the total sum of all elements in the array
5        int totalSum = accumulate(nums.begin(), nums.end(), 0);
6      
7        // Initialize the result counter and left sum accumulator
8        int validCount = 0;
9        int leftSum = 0;
10      
11        // Iterate through each element in the array
12        for (int currentNum : nums) {
13            if (currentNum != 0) {
14                // If current element is non-zero, add it to the left sum
15                leftSum += currentNum;
16            } else {
17                // If current element is zero, check if this position is valid
18              
19                // Calculate the right sum (total - left)
20                int rightSum = totalSum - leftSum;
21              
22                if (leftSum == rightSum) {
23                    // If left and right sums are equal, both directions are valid
24                    validCount += 2;
25                } else if (abs(leftSum - rightSum) == 1) {
26                    // If the difference between left and right sums is exactly 1,
27                    // one direction is valid
28                    validCount++;
29                }
30            }
31        }
32      
33        return validCount;
34    }
35};
36
1/**
2 * Counts the number of valid selections based on array elements and their sums
3 * @param nums - Array of numbers to process
4 * @returns Number of valid selections
5 */
6function countValidSelections(nums: number[]): number {
7    // Calculate the total sum of all elements in the array
8    const totalSum: number = nums.reduce((accumulator: number, currentValue: number) => accumulator + currentValue, 0);
9  
10    // Initialize result counter and left sum tracker
11    let validSelectionsCount: number = 0;
12    let leftSum: number = 0;
13  
14    // Iterate through each element in the array
15    for (const currentElement of nums) {
16        if (currentElement !== 0) {
17            // If current element is non-zero, add it to the left sum
18            leftSum += currentElement;
19        } else {
20            // If current element is zero, check for valid selection conditions
21            if (leftSum * 2 === totalSum) {
22                // Perfect balance: left sum equals right sum (both are totalSum/2)
23                // This allows selection in both directions
24                validSelectionsCount += 2;
25            } else if (Math.abs(leftSum * 2 - totalSum) <= 1) {
26                // Near balance: difference between left and right sums is at most 1
27                // This allows selection in one direction
28                validSelectionsCount++;
29            }
30        }
31    }
32  
33    return validSelectionsCount;
34}
35

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. This is because the algorithm performs two main operations:

  1. Computing the sum of all elements using sum(nums), which takes O(n) time
  2. A single pass through the array with a for loop, which also takes O(n) time

Since these operations are sequential, the overall time complexity is O(n) + O(n) = O(n).

The space complexity is O(1). The algorithm only uses a constant amount of extra space to store variables:

  • s: stores the sum of the array
  • ans: stores the count of valid selections
  • l: stores the running sum from the left
  • x: loop variable for the current element

These variables use a fixed amount of memory regardless of the input size, resulting in constant space complexity.

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

Common Pitfalls

1. Misunderstanding the Bounce Mechanics

A common mistake is thinking that when the ball hits a non-zero element, it only reverses direction without taking a step. The problem states that after bouncing, the ball takes one step in the new direction immediately.

Incorrect Understanding:

  • Ball at position i moving right
  • Hits nums[i+1] > 0
  • Decrements nums[i+1] and stays at position i+1

Correct Understanding:

  • Ball at position i moving right
  • Moves to position i+1, finds nums[i+1] > 0
  • Decrements nums[i+1], reverses to left direction
  • Takes one step left, ending at position i

2. Off-by-One Error in Direction Logic

When starting from a zero position, developers often confuse which sum (left or right) needs to be larger for a particular direction to work.

Key Insight:

  • Starting right from position i: The ball needs to handle all elements to the right first, bounce back, and then handle left elements
  • Starting left from position i: The ball needs to handle all elements to the left first, bounce back, and then handle right elements

The direction that handles the larger sum first will fail (ball exits before completing). The direction that handles the smaller sum first succeeds when the difference is at most 1.

3. Integer Overflow in Multiplication

The expression left_sum * 2 could potentially overflow for very large arrays with large values.

Solution:

# Instead of: if left_sum * 2 == total_sum:
# Use: if left_sum == total_sum - left_sum:

# Instead of: elif abs(left_sum * 2 - total_sum) == 1:
# Use: elif abs(left_sum - (total_sum - left_sum)) == 1:

4. Not Handling Edge Cases

Failing to consider arrays with:

  • All zeros: Should return 0 (no bounces needed, but problem requires process to eventually exit)
  • Single zero with non-zeros: Must verify the ball can reach all non-zero elements
  • Multiple consecutive zeros: Each zero is a potential starting point

5. Incorrect Sum Tracking

A subtle error is updating left_sum even when at a zero position:

Wrong:

for num in nums:
    left_sum += num  # Always adding, even zeros
    if num == 0:
        # Check validity

Correct:

for num in nums:
    if num != 0:
        left_sum += num  # Only add non-zero values
    else:
        # Check validity at zero position
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More