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.
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]addsvalunits 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
valat positionl(start of range) - Subtract
valat positionr+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
371class 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}
511class 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};
461function 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}
41Solution 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.
-
Initialize Difference Array: Create an array
dof lengthn + 1filled with zeros. -
Process First k Queries: For each query
[l, r, val]in the firstkqueries:- Add
valtod[l](marking the start of the range) - Subtract
valfromd[r + 1](marking the end of the range)
- Add
-
Verify Feasibility:
- Initialize a running sum
s = 0 - Iterate through each index
ifrom 0 ton-1 - Add
d[i]to the running sums - If
nums[i] > s, returnFalse(insufficient capacity)
- Initialize a running sum
-
Return
Trueif 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 EvaluatorExample 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] += 2→d = [2, 0, 0, 0]d[2] -= 2→d = [2, 0, -2, 0]
- Query 2
[1, 2, 1]:d[1] += 1→d = [2, 1, -2, 0]d[3] -= 1→d = [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
- Check:
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] += 1→d = [3, 1, -2, -1]d[3] -= 1→d = [3, 1, -2, -2]
Computing Reduction Capacity:
- Index 0:
s = 0 + 3 = 3- Check:
nums[0] = 3 ≤ 3? Yes! Can reduce to 0.
- Check:
- Index 1:
s = 3 + 1 = 4- Check:
nums[1] = 2 ≤ 4? Yes! Can reduce to 0.
- Check:
- Index 2:
s = 4 + (-2) = 2- Check:
nums[2] = 1 ≤ 2? Yes! Can reduce to 0.
- Check:
- 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
kqueries (at mostmqueries) to build the difference array:O(m) - Iterates through
numsarray 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
dof sizen + 1:O(n) - Other variables like
s,l,kuse 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.
Which technique can we use to find the middle of a linked list?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Prefix Sum Given an array of integers arr and a target value find a subarray that sums to the target Return the start and end indices of the subarray Input arr 1 20 3 30 5 4 target 7 Output 1 4 Explanation The subarray 20 3 30 from index 1 inclusive
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
Want a Structured Path to Master System Design Too? Don’t Miss This!