Facebook Pixel

3356. Zero Array Transformation II

Problem Description

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

For each query, you can perform the following action on nums:

  • Choose any value between 0 and vali (inclusive) for each index in the range [li, ri]
  • Subtract the chosen value from the element at that index
  • The amount subtracted can be different for each index within the range

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

Your task is to find the minimum number k such that after processing the first k queries in sequence, you can transform nums into a Zero Array. If it's impossible to achieve a Zero Array using any number of the given queries, return -1.

For example, if you have nums = [3, 2, 1] and a query [0, 2, 2], you could:

  • Subtract 2 from index 0 (3 becomes 1)
  • Subtract 2 from index 1 (2 becomes 0)
  • Subtract 1 from index 2 (1 becomes 0)

The key insight is that each query provides a "budget" of reductions you can apply flexibly across the specified range, and you need to determine how many queries (processed in order) are sufficient to reduce all elements to zero.

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

Intuition

The key observation is that this problem exhibits monotonic behavior: if we can transform the array into a Zero Array using the first k queries, then we can definitely do it with the first k+1 queries (or any number greater than k). This is because having more queries only gives us more "reduction power" - we never lose the ability to reduce elements.

This monotonicity immediately suggests binary search on the answer. Instead of checking every possible value of k from 1 to m (where m is the total number of queries), we can use binary search to efficiently find the minimum k.

For checking whether a specific number of queries k is sufficient, we need to determine the maximum reduction available at each index after applying the first k queries. Here's where the difference array technique becomes crucial:

  • Each query [l, r, val] adds val units of reduction capacity to all indices in range [l, r]
  • Multiple queries can overlap, so the total reduction capacity at each index is cumulative
  • We can efficiently compute these cumulative values using a difference array

The difference array works by marking the start and end of each range:

  • Add val at position l (start of range)
  • Subtract val at position r+1 (just after end of range)
  • When we compute the prefix sum, we get the actual reduction capacity at each position

For each index i, if nums[i] is greater than the total reduction capacity available at that index, then k queries are insufficient. Otherwise, we can strategically use the available reduction to bring nums[i] down to zero.

This approach transforms the problem from "how should we optimally apply each query" to "is the total reduction capacity at each index sufficient", which is much simpler to verify.

Learn more about Binary Search and Prefix Sum patterns.

Solution Implementation

1from typing import List
2
3class Solution:
4    def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:
5        n = len(nums)
6        total_queries = len(queries)
7
8        def feasible(k: int) -> bool:
9            """Check if first k queries provide enough reduction capacity."""
10            diff_array = [0] * (n + 1)
11
12            for i in range(k):
13                left, right, value = queries[i]
14                diff_array[left] += value
15                diff_array[right + 1] -= value
16
17            cumulative_sum = 0
18            for i in range(n):
19                cumulative_sum += diff_array[i]
20                if nums[i] > cumulative_sum:
21                    return False
22            return True
23
24        # Binary search template: find first k where feasible(k) is True
25        left, right = 0, total_queries
26        first_true_index = -1
27
28        while left <= right:
29            mid = (left + right) // 2
30            if feasible(mid):
31                first_true_index = mid
32                right = mid - 1  # Look for smaller k
33            else:
34                left = mid + 1
35
36        return first_true_index
37
1class Solution {
2    private int n;
3    private int[] nums;
4    private int[][] queries;
5
6    public int minZeroArray(int[] nums, int[][] queries) {
7        this.nums = nums;
8        this.queries = queries;
9        this.n = nums.length;
10        int totalQueries = queries.length;
11
12        // Binary search template: find first k where feasible(k) is True
13        int left = 0;
14        int right = totalQueries;
15        int firstTrueIndex = -1;
16
17        while (left <= right) {
18            int mid = left + (right - left) / 2;
19            if (feasible(mid)) {
20                firstTrueIndex = mid;
21                right = mid - 1;  // Look for smaller k
22            } else {
23                left = mid + 1;
24            }
25        }
26
27        return firstTrueIndex;
28    }
29
30    private boolean feasible(int k) {
31        int[] diffArray = new int[n + 1];
32
33        for (int i = 0; i < k; i++) {
34            int l = queries[i][0];
35            int r = queries[i][1];
36            int val = queries[i][2];
37            diffArray[l] += val;
38            diffArray[r + 1] -= val;
39        }
40
41        int cumulativeSum = 0;
42        for (int i = 0; i < n; i++) {
43            cumulativeSum += diffArray[i];
44            if (nums[i] > cumulativeSum) {
45                return false;
46            }
47        }
48        return true;
49    }
50}
51
1class Solution {
2public:
3    int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {
4        int n = nums.size();
5        int totalQueries = queries.size();
6
7        auto feasible = [&](int k) -> bool {
8            vector<int> diffArray(n + 1, 0);
9
10            for (int i = 0; i < k; ++i) {
11                int l = queries[i][0];
12                int r = queries[i][1];
13                int val = queries[i][2];
14                diffArray[l] += val;
15                diffArray[r + 1] -= val;
16            }
17
18            int cumulativeSum = 0;
19            for (int i = 0; i < n; ++i) {
20                cumulativeSum += diffArray[i];
21                if (nums[i] > cumulativeSum) {
22                    return false;
23                }
24            }
25            return true;
26        };
27
28        // Binary search template: find first k where feasible(k) is True
29        int left = 0;
30        int right = totalQueries;
31        int firstTrueIndex = -1;
32
33        while (left <= right) {
34            int mid = left + (right - left) / 2;
35            if (feasible(mid)) {
36                firstTrueIndex = mid;
37                right = mid - 1;  // Look for smaller k
38            } else {
39                left = mid + 1;
40            }
41        }
42
43        return firstTrueIndex;
44    }
45};
46
1function minZeroArray(nums: number[], queries: number[][]): number {
2    const n: number = nums.length;
3    const totalQueries: number = queries.length;
4
5    const feasible = (k: number): boolean => {
6        const diffArray: number[] = new Array(n + 1).fill(0);
7
8        for (let i = 0; i < k; i++) {
9            const [l, r, val] = queries[i];
10            diffArray[l] += val;
11            diffArray[r + 1] -= val;
12        }
13
14        let cumulativeSum: number = 0;
15        for (let i = 0; i < n; i++) {
16            cumulativeSum += diffArray[i];
17            if (nums[i] > cumulativeSum) {
18                return false;
19            }
20        }
21        return true;
22    };
23
24    // Binary search template: find first k where feasible(k) is True
25    let left: number = 0;
26    let right: number = totalQueries;
27    let firstTrueIndex: number = -1;
28
29    while (left <= right) {
30        const mid: number = Math.floor((left + right) / 2);
31        if (feasible(mid)) {
32            firstTrueIndex = mid;
33            right = mid - 1;  // Look for smaller k
34        } else {
35            left = mid + 1;
36        }
37    }
38
39    return firstTrueIndex;
40}
41

Solution Approach

The solution uses the boundary-finding binary search template combined with a difference array to efficiently find the minimum number of queries needed.

Binary Search Template Structure: We search for the minimum k where feasible(k) returns True. The search space is [0, m] where m is the total number of queries.

left, right = 0, m
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1  # Look for smaller k
    else:
        left = mid + 1

return first_true_index if first_true_index != -1 else -1

The Feasible Function: feasible(k) determines if the first k queries provide enough reduction capacity to transform nums into a Zero Array.

  1. Initialize Difference Array: Create an array d of length n + 1 filled with zeros.

  2. Process First k Queries: For each query [l, r, val] in the first k queries:

    • Add val to d[l] (marking the start of the range)
    • Subtract val from d[r + 1] (marking the end of the range)
  3. Verify Feasibility:

    • Initialize a running sum s = 0
    • Iterate through each index i from 0 to n-1
    • Add d[i] to the running sum s
    • If nums[i] > s, return False (insufficient capacity)
  4. Return True if all indices have sufficient capacity.

Why This Template Works: The feasible function creates a monotonic pattern: False, False, ..., True, True, True. Once we have enough queries, adding more queries only provides more reduction capacity. We use the template to find the first True (minimum sufficient k).

Time Complexity: O((m + n) × log m) where binary search runs O(log m) iterations, each check takes O(m + n).

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 Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given:

  • nums = [3, 2, 1]
  • queries = [[0, 1, 2], [1, 2, 1], [0, 2, 1]]

We need to find the minimum number of queries k such that we can transform nums into a Zero Array.

Step 1: Binary Search Setup We'll binary search on the range [0, 3] (since we have 3 queries total).

Step 2: Check if k=2 queries are sufficient

Let's check if the first 2 queries [[0, 1, 2], [1, 2, 1]] provide enough reduction capacity.

Building the Difference Array:

  • Initialize: d = [0, 0, 0, 0] (length n+1 = 4)
  • Query 1 [0, 1, 2]:
    • d[0] += 2d = [2, 0, 0, 0]
    • d[2] -= 2d = [2, 0, -2, 0]
  • Query 2 [1, 2, 1]:
    • d[1] += 1d = [2, 1, -2, 0]
    • d[3] -= 1d = [2, 1, -2, -1]

Computing Reduction Capacity via Prefix Sum:

  • Index 0: s = 0 + d[0] = 0 + 2 = 2
    • Check: nums[0] = 3 > 2? Yes! Cannot reduce 3 to 0 with only 2 units.
    • Return False

Since k=2 is insufficient, binary search will try a larger value.

Step 3: Check if k=3 queries are sufficient

Using all 3 queries [[0, 1, 2], [1, 2, 1], [0, 2, 1]]:

Building the Difference Array:

  • Initialize: d = [0, 0, 0, 0]
  • Query 1 [0, 1, 2]: d = [2, 0, -2, 0]
  • Query 2 [1, 2, 1]: d = [2, 1, -2, -1]
  • Query 3 [0, 2, 1]:
    • d[0] += 1d = [3, 1, -2, -1]
    • d[3] -= 1d = [3, 1, -2, -2]

Computing Reduction Capacity:

  • Index 0: s = 0 + 3 = 3
    • Check: nums[0] = 3 ≤ 3? Yes! Can reduce to 0.
  • Index 1: s = 3 + 1 = 4
    • Check: nums[1] = 2 ≤ 4? Yes! Can reduce to 0.
  • Index 2: s = 4 + (-2) = 2
    • Check: nums[2] = 1 ≤ 2? Yes! Can reduce to 0.
  • Return True

Step 4: Binary Search Conclusion The binary search finds that:

  • k=2 is insufficient
  • k=3 is sufficient

Therefore, the minimum k is 3.

Key Insight: The difference array elegantly tracks how reduction capacity accumulates across overlapping ranges. When we compute the prefix sum, we get the exact total reduction available at each position, making it easy to verify if we have enough "budget" to reduce each element to zero.

Time and Space Complexity

Time Complexity: O((n + m) × log m)

The algorithm uses binary search on the range [0, m] where m is the length of queries. Binary search performs O(log m) iterations.

For each binary search iteration, the check function is called, which:

  • Iterates through k queries (at most m queries) to build the difference array: O(m)
  • Iterates through nums array to verify the condition using the difference array: O(n)

Therefore, each check call takes O(n + m) time, and with O(log m) binary search iterations, the total time complexity is O((n + m) × log m).

Space Complexity: O(n)

The space complexity is determined by:

  • The difference array d of size n + 1: O(n)
  • Other variables like s, l, k use constant space: O(1)

The total space complexity is O(n), where n is the length of the nums array.

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

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A critical mistake is using the while left < right with right = mid pattern instead of the correct template.

Incorrect approach:

left, right = 0, total_queries + 1
while left < right:
    mid = (left + right) // 2
    if feasible(mid):
        right = mid
    else:
        left = mid + 1
return left if left <= total_queries else -1

Correct approach (template-compliant):

left, right = 0, total_queries
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index

The template uses while left <= right with right = mid - 1 and tracks the answer in first_true_index.

2. Incorrect Difference Array Boundary Handling

Forgetting to allocate n + 1 elements for the difference array, or incorrectly handling the right + 1 index.

Common Mistake:

# Wrong: This would cause IndexError when right == n-1
diff_array = [0] * len(nums)  # Should be len(nums) + 1
diff_array[right + 1] -= value  # Would fail for last element

Correct Approach: Always allocate one extra element to safely handle the range end marker at right + 1.

3. Not Handling the -1 Case Properly

When no valid k exists (even using all queries is insufficient), the template naturally returns -1 because first_true_index is never updated from its initial value of -1.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

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

Load More