LeetCode 3355 - Zero Array Transformation I
The problem asks whether it is possible to transform a given integer array nums into a Zero Array, where all elements are zero, using a series of decrement operations defined by queries.
Difficulty: 🟡 Medium
Topics: Array, Prefix Sum
Solution
Problem Understanding
The problem asks whether it is possible to transform a given integer array nums into a Zero Array, where all elements are zero, using a series of decrement operations defined by queries. Each query specifies a range [li, ri] in which we can select any subset of indices to decrement by 1. The operation can be applied selectively to some or all elements in the range. The task is to determine if, after performing all queries in order, nums can become an array of all zeros.
The input consists of two components: nums, an array of non-negative integers, and queries, a list of pairs indicating index ranges. The constraints are large, with both nums and queries potentially up to 10^5 elements, meaning a naive simulation of all subset selections would be computationally infeasible. Important observations include that we cannot increment numbers, and decrementing past zero is unnecessary; the minimal decrement needed at each index is exactly its value.
Edge cases to note include arrays where all elements are initially zero, queries that cover disjoint or overlapping ranges, and arrays with elements larger than the number of applicable query positions.
Approaches
Brute Force Approach
The brute-force method would try all possible subsets of each query range to decrement, recursively checking every combination to see if a zero array can be achieved. This guarantees correctness because it explores every possibility, but it is infeasible due to the combinatorial explosion of subset choices, giving a time complexity of O(2^n) per query.
Optimal Approach
The key observation is that for a zero array to be achievable, the number of queries covering each index must be at least the value of nums at that index. In other words, each element nums[i] requires at least nums[i] opportunities to be decremented. This transforms the problem into a range coverage problem, which can be efficiently checked using a prefix sum array. We increment a delta array at the start of each query range and decrement after the end, then compute the prefix sum to find the number of queries covering each index. If every index is covered at least as many times as its value, the zero array is possible.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^(n * m)) | O(n) | Tries all subsets of queries, impractical for constraints |
| Optimal | O(n + m) | O(n) | Uses prefix sum to count coverage efficiently |
Algorithm Walkthrough
- Initialize a
deltaarray of sizen + 1with all zeros. This array will track increments and decrements for range coverage. - For each query
[l, r], incrementdelta[l]by 1 and decrementdelta[r + 1]by 1. This marks the start and end of coverage for each query. - Convert the
deltaarray into acoveragearray using a prefix sum. Each element incoverage[i]now represents the total number of queries that cover indexi. - Iterate through
numsand compare each element with its corresponding coverage value. If anynums[i] > coverage[i], returnfalsebecause there are insufficient opportunities to decrement it to zero. - If all elements satisfy
nums[i] <= coverage[i], returntrue.
Why it works
The prefix sum accurately counts how many queries affect each index. Since each query allows a decrement of any subset of indices, an index that is covered c times can be decremented up to c times. Ensuring nums[i] <= coverage[i] guarantees that we have enough decrements to reduce every element to zero.
Python Solution
from typing import List
class Solution:
def isZeroArray(self, nums: List[int], queries: List[List[int]]) -> bool:
n = len(nums)
delta = [0] * (n + 1)
# Mark the range increments
for l, r in queries:
delta[l] += 1
if r + 1 < n:
delta[r + 1] -= 1
# Compute the coverage via prefix sum
coverage = [0] * n
coverage[0] = delta[0]
for i in range(1, n):
coverage[i] = coverage[i - 1] + delta[i]
# Check if each element can be decremented to zero
for i in range(n):
if nums[i] > coverage[i]:
return False
return True
The Python implementation first sets up a delta array to represent the contribution of each query range. The prefix sum converts this delta array into the number of queries covering each index. Finally, the comparison ensures that each element has enough decrements to reach zero.
Go Solution
func isZeroArray(nums []int, queries [][]int) bool {
n := len(nums)
delta := make([]int, n+1)
for _, q := range queries {
l, r := q[0], q[1]
delta[l]++
if r+1 < n {
delta[r+1]--
}
}
coverage := make([]int, n)
coverage[0] = delta[0]
for i := 1; i < n; i++ {
coverage[i] = coverage[i-1] + delta[i]
}
for i := 0; i < n; i++ {
if nums[i] > coverage[i] {
return false
}
}
return true
}
In Go, slices are used similarly to Python lists. The delta array tracks query ranges, and the prefix sum generates coverage. Boundary checks for r+1 are required to avoid index out of range errors.
Worked Examples
Example 1: nums = [1,0,1], queries = [[0,2]]
| Index | Delta after query | Coverage | nums[i] <= coverage[i]? |
|---|---|---|---|
| 0 | 1 | 1 | True |
| 1 | 1 | 1 | True |
| 2 | 1 | 1 | True |
All checks pass, so output is true.
Example 2: nums = [4,3,2,1], queries = [[1,3],[0,2]]
| Index | Delta | Coverage | nums[i] <= coverage[i]? |
|---|---|---|---|
| 0 | 1 | 1 | False (4 > 1) |
| 1 | 2 | 2 | True |
| 2 | 2 | 2 | True |
| 3 | 1 | 1 | True |
Index 0 fails the check, so output is false.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + m) | n for prefix sum and comparison, m for processing queries |
| Space | O(n) | delta and coverage arrays of size n |
This approach is linear with respect to the number of elements and queries, making it efficient for the given constraints.
Test Cases
# Provided examples
assert Solution().isZeroArray([1,0,1], [[0,2]]) == True # full coverage
assert Solution().isZeroArray([4,3,2,1], [[1,3],[0,2]]) == False # insufficient coverage
# Edge cases
assert Solution().isZeroArray([0,0,0], [[0,2]]) == True # all zeros
assert Solution().isZeroArray([1], [[0,0]]) == True # single element exact coverage
assert Solution().isZeroArray([1], [[0,0],[0,0]]) == True # multiple queries for single element
assert Solution().isZeroArray([2], [[0,0]]) == False # not enough queries
assert Solution().isZeroArray([1,1,1], [[0,1],[1,2]]) == True # overlapping queries
| Test | Why |
|---|---|
| [1,0,1], [[0,2]] | basic successful transformation |
| [4,3,2,1], [[1,3],[0,2]] | insufficient coverage triggers false |
| [0,0,0], [[0,2]] | edge case with all zeros |
| [1], [[0,0]] | single element exact coverage |
| [2], [[0,0]] | single element insufficient coverage |
| [1,1,1], [[0,1],[1,2]] | overlapping ranges are handled correctly |
Edge Cases
- All zeros initially: If
numsis already all zeros, no decrements are needed. The implementation correctly returnstruebecause the checknums[i] <= coverage[i]will pass trivially. - Single element array: Arrays with one element test the boundary conditions for indexing and query application. The delta array correctly handles a single-element range, ensuring no out-of-bounds errors.
- Insufficient coverage: If any index requires more decrements than the total queries covering it, the algorithm must return
false. The prefix sum comparison guarantees this by checkingnums[i] > coverage[i].
This solution correctly handles these cases by relying on precise range coverage counting rather than simulating every possible subset of query decrements.