Zero Array Transformation IV
MedPrompt
You are given an integer array nums of length n and a 2D array queries, where queries[i] = [li, ri, vali].
Each queries[i] represents the following action on nums:
- Select a subset of indices in the range
[li, ri]fromnums. - Decrement the value at each selected index by exactly
vali.
A Zero Array is an array with all its elements equal to 0.
Return the minimum possible non-negative value of k, such that after processing the first k queries in sequence, nums becomes a Zero Array. If no such k exists, return -1.
Example 1:
Input: nums = [2,0,2], queries = [[0,2,1],[0,2,1],[1,1,3]]
Output: 2
Explanation:
- For query 0 (l = 0, r = 2, val = 1):
- Decrement the values at indices
[0, 2]by 1. - The array will become
[1, 0, 1].
- Decrement the values at indices
- For query 1 (l = 0, r = 2, val = 1):
- Decrement the values at indices
[0, 2]by 1. - The array will become
[0, 0, 0], which is a Zero Array. Therefore, the minimum value ofkis 2.
- Decrement the values at indices
Example 2:
Input: nums = [4,3,2,1], queries = [[1,3,2],[0,2,1]]
Output: -1
Explanation:
It is impossible to make nums a Zero Array even after all the queries.
Example 3:
Input: nums = [1,2,3,2,1], queries = [[0,1,1],[1,2,1],[2,3,2],[3,4,1],[4,4,1]]
Output: 4
Explanation:
- For query 0 (l = 0, r = 1, val = 1):
- Decrement the values at indices
[0, 1]by1. - The array will become
[0, 1, 3, 2, 1].
- Decrement the values at indices
- For query 1 (l = 1, r = 2, val = 1):
- Decrement the values at indices
[1, 2]by 1. - The array will become
[0, 0, 2, 2, 1].
- Decrement the values at indices
- For query 2 (l = 2, r = 3, val = 2):
- Decrement the values at indices
[2, 3]by 2. - The array will become
[0, 0, 0, 0, 1].
- Decrement the values at indices
- For query 3 (l = 3, r = 4, val = 1):
- Decrement the value at index 4 by 1.
- The array will become
[0, 0, 0, 0, 0]. Therefore, the minimum value ofkis 4.
Example 4:
Input: nums = [1,2,3,2,6], queries = [[0,1,1],[0,2,1],[1,4,2],[4,4,4],[3,4,1],[4,4,5]]
Output: 4
Constraints:
1 <= nums.length <= 100 <= nums[i] <= 10001 <= queries.length <= 1000queries[i] = [li, ri, vali]0 <= li <= ri < nums.length1 <= vali <= 10
Approaches
3 approaches with complexity analysis and trade-offs.
This approach simulates the process for every possible number of queries k, from 1 up to the total number of queries. For each k, it checks if it's possible to make the array nums all zeros. This check is done by solving a subset sum problem for each element nums[j]. The subset sum problem itself is solved using a brute-force backtracking (or recursive) method, which explores all possibilities.
Algorithm
- For each possible number of queries
kfrom 1 toqueries.length:- Call a function
canMakeZero(k)to check ifnumscan be turned into a zero array. - If
canMakeZero(k)returnstrue, thenkis the minimum number of queries, so returnk.
- Call a function
- If the loop completes without finding a solution, return -1.
- The
canMakeZero(k)function checks each indexjofnums:- It gathers all
val_ifrom the firstkqueries that are applicable to indexj. - It uses a recursive backtracking function to check if
nums[j]can be formed by a sum of a subset of these values. - If any index
jfails this check,canMakeZero(k)returnsfalse. - If all indices pass, it returns
true.
- It gathers all
Walkthrough
The core idea is to test each potential answer k sequentially. For a given k, we must determine if there's a way to apply the first k queries to make every nums[j] zero. This decomposes into n independent subset sum problems. For each nums[j], we need to find if it can be expressed as a sum of some of the val_i from the first k queries that cover index j.
The backtracking function canFormSum explores all subsets of the applicable query values to see if any sum up to the target nums[j]. It does this by making two recursive calls at each step: one where the current value is included in the sum, and one where it's excluded.
class Solution { public int zeroArray(int[] nums, int[][] queries) { for (int k = 1; k <= queries.length; k++) { if (canMakeZero(nums, queries, k)) { return k; } } return -1; } private boolean canMakeZero(int[] nums, int[][] queries, int k) { for (int j = 0; j < nums.length; j++) { if (nums[j] == 0) continue; java.util.List<Integer> options = new java.util.ArrayList<>(); for (int i = 0; i < k; i++) { if (queries[i][0] <= j && j <= queries[i][1]) { options.add(queries[i][2]); } } if (!canFormSum(nums[j], options, 0)) { return false; } } return true; } private boolean canFormSum(int target, java.util.List<Integer> options, int index) { if (target == 0) return true; if (target < 0 || index == options.size()) return false; // Exclude current option if (canFormSum(target, options, index + 1)) { return true; } // Include current option if (canFormSum(target - options.get(index), options, index + 1)) { return true; } return false; }}Complexity
Time
O(n * Q * 2^Q), where n is the length of `nums` and Q is the number of queries. For each `k` up to `Q`, we check `n` indices. The check for each index involves solving a subset sum problem on a set of up to `k` values, which takes O(2^k) time with backtracking. The total time is dominated by the largest `k`.
Space
O(Q), where Q is the number of queries. This space is used for the recursion stack depth during the backtracking process and to store the list of applicable query values for an index.
Trade-offs
Pros
Conceptually simple and easy to understand.
Directly translates the problem statement into code.
Cons
Extremely inefficient due to its exponential time complexity.
Will result in a 'Time Limit Exceeded' error for all but the smallest inputs.
This approach improves upon the brute-force method by using binary search to find the minimum k. The key observation is that the problem is monotonic. The check function, canMakeZero(k), which determines if a solution exists for a given k, is implemented efficiently using dynamic programming to solve the underlying subset sum problems.
Algorithm
- The problem has a monotonic property: if the array can be zeroed with
kqueries, it can also be zeroed withk+1queries. This allows for binary search on the answerk. - Binary search for
kin the range[0, queries.length]. - For each
midvalue in the binary search, call a helper functioncanMakeZero(mid). - The
canMakeZero(k)function checks if it's possible to zero the array using the firstkqueries.- For each index
j, it solves the subset sum problem for the targetnums[j]using values from the firstkapplicable queries. - This subset sum problem is solved efficiently using dynamic programming.
- A boolean array
dpof sizenums[j] + 1is used, wheredp[s]is true if sumsis achievable.
- For each index
- If
canMakeZero(mid)is true, we might find a better answer with fewer queries, so we search in the lower half (high = mid - 1). Otherwise, we need more queries and search in the upper half (low = mid + 1).
Walkthrough
Instead of a linear scan for k, we can significantly speed up the search by using binary search. The search space for k is from 0 to queries.length.
For a given k (let's say mid in the binary search), the canMakeZero(k) function determines feasibility. For each index j, we need to check if nums[j] can be formed by summing a subset of values from the first k queries that apply to j. This is a classic subset sum problem, which we solve with dynamic programming to avoid the exponential complexity of backtracking.
We create a boolean DP array dp of size nums[j] + 1. dp[s] is true if a total decrement of s is possible. We initialize dp[0] = true. Then, for each applicable query value v, we update the dp table: dp[s] = dp[s] || dp[s - v] for s from nums[j] down to v. If dp[nums[j]] is false after considering all applicable queries, then k is not sufficient.
class Solution { public int zeroArray(int[] nums, int[][] queries) { int low = 0, high = queries.length; int ans = -1; while (low <= high) { int mid = low + (high - low) / 2; if (canMakeZero(nums, queries, mid)) { ans = mid; high = mid - 1; } else { low = mid + 1; } } return ans; } private boolean canMakeZero(int[] nums, int[][] queries, int k) { if (k == 0) { for (int num : nums) { if (num != 0) return false; } return true; } for (int j = 0; j < nums.length; j++) { if (nums[j] == 0) continue; boolean[] dp = new boolean[nums[j] + 1]; dp[0] = true; for (int i = 0; i < k; i++) { if (queries[i][0] <= j && j <= queries[i][1]) { int val = queries[i][2]; for (int s = nums[j]; s >= val; s--) { dp[s] = dp[s] || dp[s - val]; } } } if (!dp[nums[j]]) { return false; } } return true; }}Complexity
Time
O(log(Q) * n * Q * max(nums)). The binary search performs `O(log Q)` iterations. In each iteration, `canMakeZero` is called. This function iterates through `n` indices, and for each, it iterates through `k` (up to `Q`) queries and updates a DP table of size `max(nums)`. This gives a complexity of `O(n * k * max(nums))` for the check.
Space
O(max(nums)), where `max(nums)` is the maximum value in the `nums` array. This space is required for the DP table within the `canMakeZero` function. The DP table is recreated for each index check.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Reduces the search space for
kfrom linear to logarithmic.
Cons
More complex to implement than a simple iterative approach.
The time complexity, while polynomial, can still be high if
max(nums)orqueries.lengthare large.
This is the most efficient approach. Instead of binary searching for k, we process the queries one by one in their given order. We maintain a dynamic programming state that tracks all possible total decrements for each index j. After processing each query, we update this state and check if it's now possible to make the entire array zero. The first time this condition is met gives us the minimum k.
Algorithm
- Initialize a 2D boolean DP table
possible[n][max_val + 1], wheremax_valis the maximum value innums. possible[j][s]will store whether a total decrement ofsis achievable for indexj.- For each index
j, initializepossible[j][0] = true. - Iterate through the queries from
i = 0toqueries.length - 1.- For the current query
[l, r, val], update thepossibletable for all affected indicesjfromltor. - The update rule is:
possible[j][s] = possible[j][s] || possible[j][s - val]forsfrommax_valdown toval.
- For the current query
- After processing each query
i, check if a solution has been found:- Assume a solution is possible (
all_zeroable = true). - For each index
j, check ifpossible[j][nums[j]]is true. If not, setall_zeroable = falseand break. - If
all_zeroableis still true, we have found the minimumk. Returni + 1.
- Assume a solution is possible (
- If the loop finishes, no solution was found. Return -1.
Walkthrough
This approach builds the solution incrementally. We maintain a DP table, possible[j][s], which tells us if a sum s can be formed for index j using the queries processed so far. We iterate through the queries one by one. For each query, we update the possible sums for all indices it affects. After each query i is processed, we check if it's now possible to zero out the entire array. This is done by checking if for every index j, possible[j][nums[j]] is true. If this condition holds, we have found our answer, and it must be the minimum k because we are processing queries in order. The minimum number of queries is therefore i + 1.
This method avoids the overhead of binary search and re-computation within the check function, leading to a more direct and often faster solution.
class Solution { public int zeroArray(int[] nums, int[][] queries) { int n = nums.length; int maxVal = 0; for (int num : nums) { maxVal = Math.max(maxVal, num); } if (maxVal == 0) { return 0; } boolean[][] possible = new boolean[n][maxVal + 1]; for (int j = 0; j < n; j++) { possible[j][0] = true; } for (int i = 0; i < queries.length; i++) { int l = queries[i][0]; int r = queries[i][1]; int val = queries[i][2]; for (int j = l; j <= r; j++) { for (int s = maxVal; s >= val; s--) { possible[j][s] = possible[j][s] || possible[j][s - val]; } } boolean allZeroable = true; for (int j = 0; j < n; j++) { if (!possible[j][nums[j]]) { allZeroable = false; break; } } if (allZeroable) { return i + 1; } } return -1; }}Complexity
Time
O(Q * n * max(nums)). We iterate through `Q` queries. For each query, we iterate through at most `n` indices. For each of those indices, we perform an update that takes `O(max(nums))` time. The check after each query takes an additional `O(n)` time, which is dominated.
Space
O(n * max(nums)). This space is used to store the main DP table `possible`. Given the constraints `n <= 10` and `max(nums) <= 1000`, this is `10 * 1001`, which is feasible.
Trade-offs
Pros
Most efficient time complexity for the given constraints.
Conceptually straightforward, as it builds the solution incrementally.
Avoids the overhead of binary search.
Cons
Requires significant memory for the DP table, which could be an issue if
normax(nums)were much larger.
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.