2772. Apply Operations to Make All Array Elements Equal to Zero
Problem Description
You have an integer array nums
(0-indexed) and a positive integer k
.
You can perform this operation as many times as you want:
- Select any contiguous subarray of exactly
k
elements - Decrease every element in that subarray by 1
Your goal is to determine if it's possible to make all elements in the array equal to 0 through these operations.
Return true
if you can reduce all array elements to 0, or false
if it's impossible.
Key Points:
- A subarray must be contiguous (elements next to each other)
- Each operation affects exactly
k
consecutive elements - You subtract 1 from all elements in the chosen subarray per operation
- You can perform the operation any number of times
- You can choose any valid subarray of size
k
for each operation
Example scenarios:
- If you have
nums = [2, 2, 3, 1, 1, 0]
andk = 3
, you could:- Apply operation on indices 0-2:
[1, 1, 2, 1, 1, 0]
- Apply operation on indices 1-3:
[1, 0, 1, 0, 1, 0]
- Apply operation on indices 0-2:
[0, -1, 0, 0, 1, 0]
- This would result in a negative number, which means we've gone too far
- Apply operation on indices 0-2:
The challenge is determining whether there exists a sequence of operations that reduces every element to exactly 0, without any element going negative at any point.
Intuition
Let's think about this problem step by step. When we look at the leftmost element nums[0]
, we have a crucial observation: this element can only be decreased by operations that start at index 0. Why? Because any operation starting at index 1 or later won't include nums[0]
.
So if nums[0] = 5
, we must perform exactly 5 operations starting at index 0. There's no other way to make nums[0]
equal to 0. This means we'll decrease elements nums[0]
through nums[k-1]
by 5.
After handling nums[0]
, we move to nums[1]
. At this point, nums[1]
might have already been reduced by some amount from the operations we did for nums[0]
. Whatever value remains at nums[1]
, that's the exact number of operations we need to perform starting at index 1.
This pattern continues: for each position i
, we must perform exactly nums[i]
operations (after accounting for previous operations) starting at position i
.
Now, how do we efficiently track the effect of all these operations? If we naively update all k
elements for each operation, we'd have a time complexity issue. This is where the difference array technique comes in.
Instead of directly modifying the array elements, we can track the "changes" at specific positions:
- When we start an operation at position
i
that decreasesk
elements by some valuex
, we mark that the decrease starts ati
(subtractx
from our running sum) - We mark that the decrease ends at position
i+k
(addx
back to restore the running sum)
By maintaining a running sum s
of these changes as we traverse the array, we can determine the actual value at each position after all previous operations.
The key insight is that we process the array from left to right, and at each position, we know exactly how many operations we need to perform. If at any point:
- The remaining value becomes negative (we've decreased too much)
- We need to perform an operation but don't have enough elements remaining (i + k > n)
Then it's impossible to make all elements 0, and we return false
.
Learn more about Prefix Sum patterns.
Solution Approach
Let's implement the solution using a difference array combined with prefix sum to efficiently track the operations.
Step 1: Initialize the data structures
- Create a difference array
d
of sizen + 1
to track where operations start and end - Initialize a running sum
s = 0
to track the cumulative effect of operations
Step 2: Process each element from left to right
For each position i
with value nums[i]
:
-
Update the running sum: Add
d[i]
tos
. This accounts for any operations that start or end at positioni
. -
Calculate the actual value: Add
s
tonums[i]
. This gives us the current value after all previous operations have been applied.x = nums[i] + s
-
Check if already zero: If
x = 0
, we can skip to the next element. -
Validate the operation:
- If
x < 0
, it means previous operations have reduced this element below 0, which is invalid. Returnfalse
. - If
i + k > n
, we don't have enough elements to perform a subarray operation of sizek
. Returnfalse
.
- If
-
Apply the operation:
- We need to perform
x
operations starting at positioni
- Update the running sum:
s -= x
(this affects positionsi
throughi+k-1
) - Mark where the operation ends:
d[i + k] += x
(at positioni+k
, we restore the sum)
- We need to perform
Why this works:
- The difference array
d[i]
stores the change that occurs at positioni
- When we subtract
x
froms
, it affects all subsequent positions until we add it back atd[i + k]
- This simulates decreasing elements in the range
[i, i+k-1]
byx
Example walkthrough:
If nums = [2, 2, 3, 1, 1, 0]
and k = 3
:
- Position 0:
x = 2
, perform 2 operations on[0, 2]
, sets = -2
,d[3] = 2
- Position 1:
s = -2
,x = 2 + (-2) = 0
, skip - Position 2:
s = -2
,x = 3 + (-2) = 1
, perform 1 operation on[2, 4]
, sets = -3
,d[5] = 1
- Position 3:
s = -3 + 2 = -1
(d[3] = 2),x = 1 + (-1) = 0
, skip - Position 4:
s = -1
,x = 1 + (-1) = 0
, skip - Position 5:
s = -1 + 1 = 0
(d[5] = 1),x = 0 + 0 = 0
, skip
All elements can be reduced to 0, so return true
.
Time Complexity: O(n)
- single pass through the array
Space Complexity: O(n)
- for the difference array
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example: nums = [3, 1, 2]
and k = 2
We'll use a difference array d
and running sum s
to track operations.
Initial Setup:
d = [0, 0, 0, 0]
(size n+1 = 4)s = 0
Processing each position:
Position 0 (nums[0] = 3):
- Update running sum:
s = s + d[0] = 0 + 0 = 0
- Calculate actual value:
x = nums[0] + s = 3 + 0 = 3
- Since
x = 3 > 0
, we need to perform 3 operations starting at position 0 - Check if valid:
i + k = 0 + 2 = 2 ≤ 3
✓ - Apply operation:
s = s - x = 0 - 3 = -3
(this affects positions 0 and 1)d[i + k] = d[2] = 3
(mark where the effect ends)
- State:
d = [0, 0, 3, 0]
,s = -3
Position 1 (nums[1] = 1):
- Update running sum:
s = s + d[1] = -3 + 0 = -3
- Calculate actual value:
x = nums[1] + s = 1 + (-3) = -2
- Since
x = -2 < 0
, this means position 1 has been decreased below 0 by previous operations - Return
false
- it's impossible to make all elements 0
Why it failed:
When we performed 3 operations starting at position 0, we decreased both nums[0]
and nums[1]
by 3. This made nums[0]
go from 3 to 0 (good!), but nums[1]
went from 1 to -2 (bad!). We can't have negative values at any point.
Let's try another example that works: nums = [2, 1, 1]
and k = 2
Initial Setup:
d = [0, 0, 0, 0]
s = 0
Position 0 (nums[0] = 2):
- Update running sum:
s = 0 + d[0] = 0
- Calculate actual value:
x = 2 + 0 = 2
- Apply 2 operations starting at position 0
s = -2
,d[2] = 2
Position 1 (nums[1] = 1):
- Update running sum:
s = -2 + d[1] = -2
- Calculate actual value:
x = 1 + (-2) = -1
- Since
x = -1 < 0
, returnfalse
This also fails! The issue is that the operations on position 0 affect position 1 too much.
Final working example: nums = [1, 1, 2]
and k = 2
Position 0 (nums[0] = 1):
s = 0
,x = 1 + 0 = 1
- Apply 1 operation starting at position 0
s = -1
,d[2] = 1
Position 1 (nums[1] = 1):
s = -1 + 0 = -1
x = 1 + (-1) = 0
- Since
x = 0
, skip (already zero after previous operations)
Position 2 (nums[2] = 2):
s = -1 + 1 = 0
(d[2] = 1 restores the sum)x = 2 + 0 = 2
- Need to apply 2 operations, but
i + k = 2 + 2 = 4 > 3
- Return
false
- not enough elements for the operation
This demonstrates how the algorithm correctly identifies when operations are impossible due to insufficient array length.
Solution Implementation
1class Solution:
2 def checkArray(self, nums: List[int], k: int) -> bool:
3 """
4 Check if we can make all elements zero by repeatedly choosing k consecutive
5 elements and decreasing them by the same positive value.
6
7 Args:
8 nums: List of integers to process
9 k: Number of consecutive elements to decrease in each operation
10
11 Returns:
12 True if all elements can be made zero, False otherwise
13 """
14 n = len(nums)
15
16 # difference_array tracks when decrements end
17 # difference_array[i] represents the change in cumulative decrement at position i
18 difference_array = [0] * (n + 1)
19
20 # cumulative_decrement tracks the total decrement applied at current position
21 cumulative_decrement = 0
22
23 for i, current_value in enumerate(nums):
24 # Update cumulative decrement with the change at position i
25 cumulative_decrement += difference_array[i]
26
27 # Calculate the effective value after applying cumulative decrements
28 effective_value = current_value + cumulative_decrement
29
30 # If effective value is already 0, no operation needed at this position
31 if effective_value == 0:
32 continue
33
34 # Check if value is negative (over-decremented) or if we can't form a window of size k
35 if effective_value < 0 or i + k > n:
36 return False
37
38 # Apply a new decrement operation starting at position i
39 # This decreases k consecutive elements by effective_value
40 cumulative_decrement -= effective_value
41
42 # Mark where this decrement operation ends (at position i + k)
43 difference_array[i + k] += effective_value
44
45 return True
46
1class Solution {
2 public boolean checkArray(int[] nums, int k) {
3 int n = nums.length;
4
5 // Difference array to track when operations end
6 // differenceArray[i] represents the change in running sum at position i
7 int[] differenceArray = new int[n + 1];
8
9 // Running sum to track the cumulative effect of all active operations
10 int runningSum = 0;
11
12 for (int i = 0; i < n; ++i) {
13 // Apply the change at current position from ending operations
14 runningSum += differenceArray[i];
15
16 // Update the current element with the effect of all active operations
17 nums[i] += runningSum;
18
19 // If current element is already zero, move to next element
20 if (nums[i] == 0) {
21 continue;
22 }
23
24 // Check if current element is negative (over-subtracted) or
25 // if we don't have enough elements to perform a k-length operation
26 if (nums[i] < 0 || i + k > n) {
27 return false;
28 }
29
30 // Start a new operation to make current element zero
31 // Subtract nums[i] from the running sum (operation starts here)
32 runningSum -= nums[i];
33
34 // Mark where this operation ends (after k elements)
35 // Add nums[i] back to restore the running sum after k positions
36 differenceArray[i + k] += nums[i];
37 }
38
39 // If we reach here, all elements can be made zero
40 return true;
41 }
42}
43
1class Solution {
2public:
3 bool checkArray(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Difference array to track range updates efficiently
7 // diff[i] represents the change to be applied starting from index i
8 vector<int> diff(n + 1, 0);
9
10 // Running sum of differences (cumulative effect of all operations)
11 int cumulativeEffect = 0;
12
13 for (int i = 0; i < n; ++i) {
14 // Apply the cumulative effect from previous operations
15 cumulativeEffect += diff[i];
16 nums[i] += cumulativeEffect;
17
18 // If current element is already 0, no operation needed
19 if (nums[i] == 0) {
20 continue;
21 }
22
23 // Check if current element is negative (impossible to achieve)
24 // or if we don't have enough elements for a k-length subarray
25 if (nums[i] < 0 || i + k > n) {
26 return false;
27 }
28
29 // Perform operation: subtract nums[i] from next k elements
30 // Update cumulative effect for current position
31 cumulativeEffect -= nums[i];
32
33 // Mark the end of this operation's effect after k elements
34 diff[i + k] += nums[i];
35 }
36
37 // All elements have been successfully reduced to 0
38 return true;
39 }
40};
41
1/**
2 * Checks if an array can be made all zeros by repeatedly subtracting 1 from k consecutive elements
3 * @param nums - The input array of numbers
4 * @param k - The size of consecutive elements to operate on
5 * @returns true if the array can be made all zeros, false otherwise
6 */
7function checkArray(nums: number[], k: number): boolean {
8 const arrayLength: number = nums.length;
9
10 // Difference array to track range updates efficiently
11 // Used to mark where operations end (at index i+k)
12 const differenceArray: number[] = Array(arrayLength + 1).fill(0);
13
14 // Running sum to track the cumulative effect of operations
15 let cumulativeSum: number = 0;
16
17 // Process each element in the array
18 for (let i = 0; i < arrayLength; ++i) {
19 // Apply accumulated operations from previous iterations
20 cumulativeSum += differenceArray[i];
21 nums[i] += cumulativeSum;
22
23 // If current element is already zero, skip to next
24 if (nums[i] === 0) {
25 continue;
26 }
27
28 // Check if operation is valid:
29 // 1. Element cannot be negative (we can only subtract, not add)
30 // 2. Must have enough elements remaining for k-length operation
31 if (nums[i] < 0 || i + k > arrayLength) {
32 return false;
33 }
34
35 // Perform operation: subtract nums[i] from next k elements
36 // Update cumulative sum for immediate effect
37 cumulativeSum -= nums[i];
38
39 // Mark where this operation ends (after k elements)
40 differenceArray[i + k] += nums[i];
41 }
42
43 return true;
44}
45
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. The algorithm iterates through the array exactly once with a single for loop that processes each element. Within each iteration, all operations (array access, arithmetic operations, and comparisons) are performed in constant time O(1)
.
The space complexity is O(n)
, where n
is the length of the array nums
. The algorithm creates an auxiliary array d
of size n + 1
to store the difference values. Additionally, it uses a constant amount of extra space for variables s
, i
, and x
, but these don't affect the overall space complexity which is dominated by the array d
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Operation Direction
A common mistake is thinking we need to track which operations to perform rather than greedily applying operations from left to right. Some might try to find an optimal sequence of operations using backtracking or dynamic programming, which overcomplicates the problem.
Why this happens: The problem statement allows choosing "any valid subarray," which might suggest we need to consider all possible orderings.
Solution: Recognize that if a solution exists, there's always a valid sequence where we process elements from left to right. Once we reach position i
, we must reduce it to exactly 0 before moving on, as we can't apply operations that start before position i
once we've passed it.
2. Incorrect Handling of the Difference Array
Developers often make errors when updating the difference array, particularly:
- Forgetting to add the restoration value at position
i + k
- Using
difference_array[i + k - 1]
instead ofdifference_array[i + k]
(the operation affects indices[i, i+k-1]
, so the restoration happens at indexi + k
)
Example of incorrect code:
# WRONG: This would restore the value one position too early difference_array[i + k - 1] += effective_value # CORRECT: Restore at position i + k (after the k-element window) difference_array[i + k] += effective_value
3. Not Accounting for Cumulative Effects
A critical mistake is forgetting that operations accumulate. When processing position i
, you must consider all previous operations that still affect this position.
Example of incorrect approach:
# WRONG: Only checking the original value if nums[i] < 0: return False # CORRECT: Check the effective value after all cumulative operations effective_value = nums[i] + cumulative_decrement if effective_value < 0: return False
4. Off-by-One Errors in Boundary Checking
When checking if we can perform an operation starting at position i
, the condition should be i + k > n
, not i + k >= n
or i + k - 1 > n
.
Why: An operation starting at position i
affects positions [i, i+k-1]
. The last valid starting position is when i + k - 1 = n - 1
, which means i + k = n
.
# WRONG: This would reject valid operations at the boundary if i + k >= n: # or if i + k - 1 >= n: return False # CORRECT: Allow operations where the last affected element is at index n-1 if i + k > n: return False
5. Modifying the Original Array
Some solutions mistakenly modify nums[i]
directly instead of tracking the effective value separately:
# WRONG: This modifies the input and loses track of the original values nums[i] += cumulative_decrement if nums[i] < 0: return False # CORRECT: Calculate effective value without modifying the original array effective_value = nums[i] + cumulative_decrement if effective_value < 0: return False
This is problematic because:
- It modifies the input array (poor practice unless explicitly allowed)
- Future iterations might need the original values for validation
- It makes debugging harder as you lose the initial state
Which algorithm should you use to find a node that is close to the root of the tree?
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!