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:
-
Starting Setup: Choose a starting position
curr
wherenums[curr] == 0
(must be a zero), and pick an initial movement direction (either left or right). -
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 tocurr + 1
) - If moving left: decrement
curr
(move tocurr - 1
)
- If moving right: increment
- 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
- Decrease
- If
-
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
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:
-
Calculate Total Sum: First, compute the total sum
s
of all elements in the array. This represents the total number of "bounces" needed. -
Track Left Sum: Initialize variables
ans = 0
(to count valid selections) andl = 0
(to track the sum of elements to the left of current position). -
Iterate Through Array: For each element
x
innums
:- If
x
is non-zero: Add it to the left suml
(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 tol == 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
- If
- Calculate right sum as
- If
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
meansl - (s - l) == 1
(left sum is 1 more than right)l * 2 - s == -1
meansl - (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 EvaluatorExample 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) andl = 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
, sol * 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
, sol * 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
, sol * 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:
- Computing the sum of all elements using
sum(nums)
, which takesO(n)
time - 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 arrayans
: stores the count of valid selectionsl
: stores the running sum from the leftx
: 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 positioni+1
Correct Understanding:
- Ball at position
i
moving right - Moves to position
i+1
, findsnums[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
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!