Make Array Elements Equal to Zero
EasyPrompt
You are given an integer array nums.
Start by selecting a starting position curr such that nums[curr] == 0, and choose a movement direction of either left or right.
After that, you repeat the following process:
- If
curris out of the range[0, n - 1], this process ends. - If
nums[curr] == 0, move in the current direction by incrementingcurrif you are moving right, or decrementingcurrif you are moving left. - Else if
nums[curr] > 0:- Decrement
nums[curr]by 1. - Reverse your movement direction (left becomes right and vice versa).
- Take a step in your new direction.
- Decrement
A selection of the initial position curr and movement direction is considered valid if every element in nums becomes 0 by the end of the process.
Return the number of possible valid selections.
Example 1:
Input: nums = [1,0,2,0,3]
Output: 2
Explanation:
The only possible valid selections are the following:
- Choose
curr = 3, and a movement direction to the left.[1,0,2,0,3] -> [1,0,2,0,3] -> [1,0,1,0,3] -> [1,0,1,0,3] -> [1,0,1,0,2] -> [1,0,1,0,2] -> [1,0,0,0,2] -> [1,0,0,0,2] -> [1,0,0,0,1] -> [1,0,0,0,1] -> [1,0,0,0,1] -> [1,0,0,0,1] -> [0,0,0,0,1] -> [0,0,0,0,1] -> [0,0,0,0,1] -> [0,0,0,0,1] -> [0,0,0,0,0].
- Choose
curr = 3, and a movement direction to the right.[1,0,2,0,3] -> [1,0,2,0,3] -> [1,0,2,0,2] -> [1,0,2,0,2] -> [1,0,1,0,2] -> [1,0,1,0,2] -> [1,0,1,0,1] -> [1,0,1,0,1] -> [1,0,0,0,1] -> [1,0,0,0,1] -> [1,0,0,0,0] -> [1,0,0,0,0] -> [1,0,0,0,0] -> [1,0,0,0,0] -> [0,0,0,0,0].
Example 2:
Input: nums = [2,3,4,0,4,1,0]
Output: 0
Explanation:
There are no possible valid selections.
Constraints:
1 <= nums.length <= 1000 <= nums[i] <= 100- There is at least one element
iwherenums[i] == 0.
Approaches
2 approaches with complexity analysis and trade-offs.
The brute-force simulation can be slow because of the time spent traversing blocks of zeros one step at a time. We can optimize this by skipping over consecutive zeros in a single operation. A TreeSet is an effective data structure for this purpose, as it can maintain the sorted indices of non-zero elements and find the next one in a given direction in logarithmic time.
Algorithm
- The overall structure is the same as the brute-force approach: iterate through all
(start_position, start_direction)pairs. - For each simulation, instead of a simple array, use a
TreeSetto store the indices of all non-zero elements. This allows for efficient queries of the next non-zero index. - Initialize the simulation with a copy of
nums, theTreeSetof non-zero indices,curr, anddir. - Loop as long as
curris within bounds: a. Ifnums_copy[curr] > 0: i. Decrementnums_copy[curr]. ii. Ifnums_copy[curr]becomes 0, removecurrfrom theTreeSet. iii. Reverse direction and move:dir *= -1,curr += dir. b. Ifnums_copy[curr] == 0: i. Instead of moving one step at a time, find the next non-zero element in the current direction using theTreeSet. ii. If moving right (dir=1), find the smallest index in theTreeSetthat is greater thancurr. This is done viatreeSet.ceiling(curr + 1). iii. If moving left (dir=-1), find the largest index in theTreeSetthat is less thancurr. This is done viatreeSet.floor(curr - 1). iv. If a next non-zero index is found, jumpcurrto that index. Otherwise, jumpcurrout of bounds. - After the loop terminates, the selection is valid if and only if the
TreeSetof non-zero indices is empty.
Walkthrough
In this approach, we enhance the simulation by maintaining a TreeSet of indices where the array value is positive. When the simulation is at a position curr with a value of 0, instead of moving one step, we query the TreeSet to find the index of the next non-zero element in the current direction. This allows us to jump over entire blocks of zeros instantly.
For a rightward movement, treeSet.ceiling(curr + 1) gives us the next non-zero index. For a leftward movement, treeSet.floor(curr - 1) provides the previous non-zero index. If no such index exists, it means we've moved past all remaining non-zero numbers in that direction, and we can jump curr out of bounds to terminate that path segment.
When a non-zero element at an index is decremented to zero, we must remove that index from the TreeSet. This keeps our set of non-zero indices up-to-date. This optimization significantly reduces the number of steps in the simulation, especially for sparse arrays with large blocks of zeros.
class Solution { public int makeArrayElementsEqualtoZero(int[] nums) { int n = nums.length; java.util.List<Integer> zeroIndices = new java.util.ArrayList<>(); for (int i = 0; i < n; i++) { if (nums[i] == 0) { zeroIndices.add(i); } } int validCount = 0; for (int startIdx : zeroIndices) { if (isSelectionValid(nums.clone(), startIdx, 1)) { validCount++; } if (isSelectionValid(nums.clone(), startIdx, -1)) { validCount++; } } return validCount; } private boolean isSelectionValid(int[] currentNums, int startCurr, int startDir) { int n = currentNums.length; java.util.TreeSet<Integer> nonZeroIndices = new java.util.TreeSet<>(); for (int i = 0; i < n; i++) { if (currentNums[i] > 0) { nonZeroIndices.add(i); } } if (nonZeroIndices.isEmpty()) { return true; } int curr = startCurr; int dir = startDir; while (curr >= 0 && curr < n) { if (currentNums[curr] > 0) { currentNums[curr]--; if (currentNums[curr] == 0) { nonZeroIndices.remove(curr); } dir *= -1; curr += dir; } else { // currentNums[curr] == 0 if (dir == 1) { Integer nextNonZero = nonZeroIndices.ceiling(curr + 1); curr = (nextNonZero != null) ? nextNonZero : n; } else { // dir == -1 Integer prevNonZero = nonZeroIndices.floor(curr - 1); curr = (prevNonZero != null) ? prevNonZero : -1; } } } return nonZeroIndices.isEmpty(); }}Complexity
Time
O(Z * (n log n + S * log n)), where Z is the number of zeros, S is the sum of elements, and n is the length. For each of the `Z` starting configurations, we spend `O(n log n)` to initialize the `TreeSet`. The simulation involves `S` decrements, and each step (decrement or jump) takes `O(log n)` time. This complexity is efficient enough for the given constraints.
Space
O(n), where n is the length of `nums`. Space is used for the array copy and the `TreeSet`, which can store up to `n` indices.
Trade-offs
Pros
Significantly faster than the naive simulation, especially for sparse arrays.
The time complexity is well within typical limits for competitive programming platforms.
Cons
Slightly more complex to implement due to the use of a
TreeSet.
This approach directly translates the process described in the problem into code. We test every possible valid starting configuration. A starting configuration is defined by an initial index curr where nums[curr] is 0, and an initial direction (left or right).
Algorithm
- Identify all initial positions
currwherenums[curr] == 0. - For each such
curr, iterate through the two possible initial directions: left (-1) and right (+1). - For each pair of
(start_position, start_direction), perform a full simulation of the process described. - To do this, create a copy of the
numsarray to modify during the simulation. - Initialize
curranddirwith the starting pair. - Loop as long as
curris within the array bounds[0, n-1]: a. Ifnums_copy[curr] == 0, move one step in the current direction:curr += dir. b. Ifnums_copy[curr] > 0, decrementnums_copy[curr]by 1, reverse the directiondir *= -1, and then take a step in the new directioncurr += dir. - Once the loop terminates (because
currgoes out of bounds), check if all elements innums_copyhave become zero. - If they are all zero, the initial selection was valid. Increment a counter for valid selections.
- After checking all possible starting configurations, return the total count.
Walkthrough
For each potential start, we simulate the entire process step-by-step. We use a copy of the input array nums for each simulation to avoid interference between different test cases. The simulation proceeds by updating the current position curr, the direction dir, and the values in the array copy according to the rules. The process is guaranteed to terminate because the sum of the elements in the array is non-increasing and strictly decreases each time we encounter a positive number.
After the simulation ends (when curr moves out of the array's bounds), we verify if all elements in the copied array have been reduced to zero. If so, we count the starting configuration as a valid one.
class Solution { public int makeArrayElementsEqualtoZero(int[] nums) { int n = nums.length; java.util.List<Integer> zeroIndices = new java.util.ArrayList<>(); for (int i = 0; i < n; i++) { if (nums[i] == 0) { zeroIndices.add(i); } } int validCount = 0; for (int startIdx : zeroIndices) { // Try starting with direction right (1) if (isSelectionValid(nums.clone(), startIdx, 1)) { validCount++; } // Try starting with direction left (-1) if (isSelectionValid(nums.clone(), startIdx, -1)) { validCount++; } } return validCount; } private boolean isSelectionValid(int[] currentNums, int startCurr, int startDir) { int n = currentNums.length; int curr = startCurr; int dir = startDir; // The process must terminate as the sum of elements decreases. // A generous step limit can prevent truly infinite loops in case of flawed logic. long totalSum = 0; for(int num : currentNums) totalSum += num; long maxSteps = totalSum * n + 2L * n; // A safe upper bound for steps long steps = 0; while (curr >= 0 && curr < n && steps < maxSteps) { if (currentNums[curr] > 0) { currentNums[curr]--; dir *= -1; } curr += dir; steps++; } for (int num : currentNums) { if (num != 0) { return false; } } return true; }}Complexity
Time
O(Z * S * n), where Z is the number of zeros in `nums`, S is the sum of elements in `nums`, and n is the length of the array. In the worst case, each of the `S` decrements might be followed by a traversal of `O(n)` zeros. Given the constraints (Z, n ≤ 100, S ≤ 100*100=10000), this could be up to `100 * 10000 * 100 = 10^8` operations, which might be too slow.
Space
O(n), where n is the length of `nums`. This is for storing a copy of the `nums` array for each simulation.
Trade-offs
Pros
Simple to understand and implement as it follows the problem description literally.
Correct for the given constraints if the execution time is within the limits.
Cons
The time complexity can be high if the sum of elements in
numsis large and there are many zero-crossings, potentially leading to a Time Limit Exceeded error on larger test cases.
Solutions
Solution
class Solution {public int countValidSelections(int[] nums) { int s = Arrays.stream(nums).sum(); int ans = 0, l = 0; for (int x : nums) { if (x != 0) { l += x; } else if (l * 2 == s) { ans += 2; } else if (Math.abs(l * 2 - s) <= 1) { ++ans; } } return ans; }}class Solution: def countValidSelections(self, nums: List[int]) -> int: s = sum(nums) ans = l = 0 for x in nums: if x: l += x elif l * 2 == s: ans += 2 elif abs(l * 2 - s) == 1: ans += 1 return ansclass Solution {public: int countValidSelections(vector<int> &nums) { int s = accumulate(nums.begin(), nums.end(), 0); int ans = 0, l = 0; for (int x : nums) { if (x) { l += x; } else if (l * 2 == s) { ans += 2; } else if (abs(l * 2 - s) <= 1) { ++ans; } } return ans; }};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.