Facebook Pixel

3355. Zero Array Transformation I

Problem Description

You have an integer array nums of length n and a 2D array queries where each query is represented as queries[i] = [li, ri].

For each query, you need to:

  1. Select any subset of indices within the range [li, ri] in the array nums (you can choose any indices between position li and ri, inclusive)
  2. Decrement the values at your selected indices by 1

A Zero Array is defined as an array where all elements equal 0.

Your task is to determine if it's possible to transform nums into a Zero Array by processing all the queries sequentially. Return true if it's possible, otherwise return false.

The key insight is that for each query [li, ri], you can optimally choose which indices to decrement within that range. You want to strategically select indices to maximize the reduction where needed. Each query gives you the opportunity to reduce values by 1 at any positions within its specified range.

For example, if nums = [1, 0, 2] and you have a query [0, 2], you could choose to decrement indices 0 and 2 (skipping index 1 since it's already 0), resulting in [0, 0, 1].

The solution uses a difference array technique to track the maximum number of times each position can be decremented across all queries. By accumulating these potential decrements and comparing with the original values in nums, we can determine if enough decrements are available at each position to reduce it to zero.

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

Intuition

The key observation is that for each query [l, r], we can choose to decrement any subset of indices within that range. This means we have complete flexibility in how to apply each query's decrements.

Think about it from each position's perspective: if position i is covered by multiple queries, we can potentially decrement it multiple times (once per covering query). For example, if position 2 is covered by queries [0, 3], [2, 5], and [1, 2], then we can decrement position 2 up to 3 times total.

This leads to a crucial insight: for each position i in nums, we need to check if the number of queries that cover position i is at least nums[i]. If yes, we can reduce nums[i] to zero by strategically using the available decrements from covering queries.

But how do we efficiently count how many queries cover each position? We could iterate through all queries for each position, but that would be inefficient.

This is where the difference array technique comes in. Instead of marking each position individually for every query, we can use a clever trick:

  • When a query [l, r] starts at position l, we mark +1 at position l
  • When it ends at position r, we mark -1 at position r + 1

By doing a prefix sum scan of these marks, we automatically get the count of active queries at each position. The prefix sum at position i tells us exactly how many queries cover that position.

Finally, we compare: if at any position i, the value nums[i] is greater than the number of available decrements (the prefix sum), then it's impossible to reduce that position to zero, and we return false. Otherwise, we have enough decrements everywhere, so we return true.

Learn more about Prefix Sum patterns.

Solution Approach

We implement the solution using a difference array technique to efficiently track the coverage of queries.

Step 1: Initialize the Difference Array

  • Create an array d of length n + 1 (one extra position for boundary handling)
  • Initialize all values to 0

Step 2: Process Each Query For each query [l, r] in queries:

  • Add 1 to d[l] (marking the start of the query range)
  • Subtract 1 from d[r + 1] (marking the end of the query range)

This difference array setup allows us to later compute how many queries cover each position using a prefix sum.

Step 3: Check if Transformation is Possible

  • Initialize a running sum s = 0
  • Iterate through each position i from 0 to n - 1:
    • Add d[i] to the running sum: s += d[i]
    • The value of s now represents the number of queries that cover position i
    • If nums[i] > s, it means we don't have enough decrements available at position i to reduce it to zero, so return False

Step 4: Return Result

  • If we successfully check all positions without finding any impossible cases, return True

Why This Works: The difference array with prefix sum gives us the exact count of how many times each position can be decremented. When we mark +1 at the start of a range and -1 at the end+1, the prefix sum automatically maintains the count of "active" query ranges at each position.

For example, if queries are [[0, 2], [1, 3]]:

  • After processing: d = [1, 1, 0, -1, -1, ...]
  • Prefix sums give: [1, 2, 2, 1, 0, ...]
  • This correctly shows position 0 is covered by 1 query, positions 1-2 are covered by 2 queries, position 3 by 1 query, etc.

The time complexity is O(n + m) where n is the length of nums and m is the number of queries, and space complexity is O(n).

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to understand how the solution works.

Example: nums = [3, 2, 1, 2], queries = [[0, 2], [1, 3], [2, 3]]

Step 1: Initialize the difference array

  • Create array d of length 5 (n + 1): d = [0, 0, 0, 0, 0]

Step 2: Process each query

Query 1: [0, 2]

  • Mark start: d[0] += 1d = [1, 0, 0, 0, 0]
  • Mark end: d[2+1] -= 1d = [1, 0, 0, -1, 0]

Query 2: [1, 3]

  • Mark start: d[1] += 1d = [1, 1, 0, -1, 0]
  • Mark end: d[3+1] -= 1d = [1, 1, 0, -1, -1]

Query 3: [2, 3]

  • Mark start: d[2] += 1d = [1, 1, 1, -1, -1]
  • Mark end: d[3+1] -= 1d = [1, 1, 1, -1, -2]

Step 3: Check if transformation is possible

Now we iterate through positions 0 to 3, maintaining a running sum to count available decrements:

  • Position 0: s = 0 + d[0] = 0 + 1 = 1
    • nums[0] = 3, available decrements = 1
    • Since 3 > 1, we cannot reduce position 0 to zero
    • Return False

The algorithm would return False because position 0 needs 3 decrements but only 1 query covers it.

Alternative example where it works: nums = [1, 2, 2, 1], same queries

Following the same process, we get d = [1, 1, 1, -1, -2]

Checking each position:

  • Position 0: s = 1, nums[0] = 1 → 1 ≤ 1 ✓
  • Position 1: s = 1 + 1 = 2, nums[1] = 2 → 2 ≤ 2 ✓
  • Position 2: s = 2 + 1 = 3, nums[2] = 2 → 2 ≤ 3 ✓
  • Position 3: s = 3 + (-1) = 2, nums[3] = 1 → 1 ≤ 2 ✓

All positions can be reduced to zero, so return True.

The prefix sum at each position tells us the maximum number of times we can decrement that position across all queries. As long as this maximum is at least as large as the original value, we can strategically choose when to apply decrements to reach zero.

Solution Implementation

1class Solution:
2    def isZeroArray(self, nums: List[int], queries: List[List[int]]) -> bool:
3        # Initialize difference array with one extra element for boundary handling
4        # This tracks the cumulative effect of queries using difference array technique
5        difference_array = [0] * (len(nums) + 1)
6      
7        # Build the difference array
8        # For each query [left, right], we increment at left and decrement at right+1
9        # This allows us to efficiently track range updates
10        for left, right in queries:
11            difference_array[left] += 1      # Start of range: increase by 1
12            difference_array[right + 1] -= 1  # End of range + 1: decrease by 1
13      
14        # Check if each element can be reduced to zero
15        # We accumulate the difference values to get the actual reduction at each position
16        cumulative_reduction = 0
17        for num_value, diff_value in zip(nums, difference_array):
18            cumulative_reduction += diff_value  # Update cumulative reduction
19          
20            # If the original value is greater than the total reduction possible,
21            # we cannot reduce this element to zero
22            if num_value > cumulative_reduction:
23                return False
24      
25        # All elements can be reduced to zero or below
26        return True
27
1class Solution {
2    public boolean isZeroArray(int[] nums, int[][] queries) {
3        int arrayLength = nums.length;
4      
5        // Create a difference array to track range updates efficiently
6        // Extra space at the end to handle boundary cases
7        int[] differenceArray = new int[arrayLength + 1];
8      
9        // Process each query and update the difference array
10        // Each query represents a range [left, right] where we can decrement values
11        for (int[] query : queries) {
12            int leftIndex = query[0];
13            int rightIndex = query[1];
14          
15            // Mark the start of the range with +1
16            differenceArray[leftIndex]++;
17          
18            // Mark the position after the end of the range with -1
19            differenceArray[rightIndex + 1]--;
20        }
21      
22        // Calculate the actual number of decrements available at each position
23        // using prefix sum on the difference array
24        int cumulativeDecrements = 0;
25      
26        for (int i = 0; i < arrayLength; i++) {
27            // Add the difference value to get total decrements at position i
28            cumulativeDecrements += differenceArray[i];
29          
30            // Check if we have enough decrements to reduce nums[i] to zero
31            // If the original value is greater than available decrements, 
32            // we cannot make this element zero
33            if (nums[i] > cumulativeDecrements) {
34                return false;
35            }
36        }
37      
38        // All elements can be reduced to zero or below
39        return true;
40    }
41}
42
1class Solution {
2public:
3    bool isZeroArray(vector<int>& nums, vector<vector<int>>& queries) {
4        int n = nums.size();
5      
6        // Create a difference array to track range updates
7        // difference[i] represents the cumulative change at position i
8        vector<int> difference(n + 1, 0);
9      
10        // Process each query to mark range decrements
11        // For range [left, right], we increment at left and decrement at right+1
12        // This allows us to efficiently apply range updates using prefix sum
13        for (const auto& query : queries) {
14            int left = query[0];
15            int right = query[1];
16            difference[left]++;      // Start of decrement range
17            difference[right + 1]--;  // End of decrement range (exclusive)
18        }
19      
20        // Check if each element can be reduced to zero
21        // Calculate prefix sum to get actual decrement count at each position
22        int cumulativeDecrements = 0;
23        for (int i = 0; i < n; ++i) {
24            cumulativeDecrements += difference[i];
25          
26            // If the original value is greater than available decrements,
27            // it cannot be reduced to zero
28            if (nums[i] > cumulativeDecrements) {
29                return false;
30            }
31        }
32      
33        return true;
34    }
35};
36
1/**
2 * Checks if an array can be reduced to all zeros by applying range decrements
3 * @param nums - The input array to check
4 * @param queries - Array of [left, right] pairs representing range decrement operations
5 * @returns true if the array can be reduced to all zeros, false otherwise
6 */
7function isZeroArray(nums: number[], queries: number[][]): boolean {
8    const arrayLength: number = nums.length;
9  
10    // Difference array to track range updates efficiently
11    // Extra element at the end to handle boundary conditions
12    const differenceArray: number[] = Array(arrayLength + 1).fill(0);
13  
14    // Apply all range updates using difference array technique
15    // For each query [left, right], we can decrement all elements in range by 1
16    for (const [leftIndex, rightIndex] of queries) {
17        differenceArray[leftIndex]++;           // Start of range increment
18        differenceArray[rightIndex + 1]--;      // End of range decrement (exclusive)
19    }
20  
21    // Calculate the actual values after all range updates using prefix sum
22    // Check if each element can be reduced to zero or below
23    let prefixSum: number = 0;
24    for (let index: number = 0; index < arrayLength; index++) {
25        prefixSum += differenceArray[index];    // Running sum gives actual decrement count at this position
26      
27        // If original value is greater than available decrements, cannot reduce to zero
28        if (nums[index] > prefixSum) {
29            return false;
30        }
31    }
32  
33    return true;
34}
35

Time and Space Complexity

Time Complexity: O(n + m)

The algorithm consists of two main parts:

  1. Processing all queries to build the difference array d, which iterates through m queries with constant time operations for each query: O(m)
  2. Iterating through the array nums and the difference array d simultaneously using zip to check if each element can be reduced to zero: O(n)

Therefore, the total time complexity is O(n + m), where n is the length of the array nums and m is the number of queries.

Space Complexity: O(n)

The algorithm creates a difference array d of size len(nums) + 1, which requires O(n) extra space. The variable s for tracking the cumulative sum uses constant space O(1). Thus, the overall space complexity is O(n).

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

Common Pitfalls

1. Off-by-One Error in Difference Array Size

A frequent mistake is creating the difference array with size n instead of n+1. This leads to an index out of bounds error when trying to mark the end of a query range at difference_array[right + 1].

Incorrect:

difference_array = [0] * len(nums)  # Wrong size!
for left, right in queries:
    difference_array[left] += 1
    difference_array[right + 1] -= 1  # IndexError when right == n-1

Correct:

difference_array = [0] * (len(nums) + 1)  # Extra element for boundary

2. Misunderstanding Query Semantics

Developers might incorrectly assume they must decrement ALL indices in the range [l, r] for each query, rather than understanding they can choose ANY subset. This leads to overly pessimistic solutions that return False when the answer should be True.

Incorrect Interpretation:

# Wrong: Assuming we must decrement everything in range
for left, right in queries:
    for i in range(left, right + 1):
        nums[i] -= 1  # This forces all indices to be decremented

Correct Understanding: The difference array tracks the MAXIMUM possible decrements available at each position, allowing optimal selection.

3. Direct Array Modification

Modifying the nums array directly while processing queries destroys the original values needed for comparison. This makes it impossible to correctly determine if transformation is possible.

Incorrect:

for left, right in queries:
    for i in range(left, right + 1):
        if nums[i] > 0:
            nums[i] -= 1  # Modifying original array
# Can't properly check anymore since nums is changed

Correct: Keep nums unchanged and track available decrements separately using the difference array technique.

4. Forgetting to Accumulate the Difference Array

Simply checking difference_array[i] >= nums[i] without accumulating the prefix sum will give incorrect results, as the difference array stores deltas, not the actual coverage count.

Incorrect:

for i in range(len(nums)):
    if nums[i] > difference_array[i]:  # Wrong! Not accumulated
        return False

Correct:

cumulative_reduction = 0
for i in range(len(nums)):
    cumulative_reduction += difference_array[i]  # Accumulate first
    if nums[i] > cumulative_reduction:
        return False
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More