Medium
题目描述
给你一个长度为 n 的整数数组 nums 和一个二维数组 queries,其中 queries[i] = [li, ri, vali]。
每个 queries[i] 表示对 nums 执行以下操作:
- 从
nums中选择范围[li, ri]内的一个索引子集 - 将每个选定索引处的值精确减少
vali
零数组是所有元素都等于 0 的数组。
返回最小可能的非负值 k,使得在按顺序处理前 k 个查询后,nums 变成零数组。如果不存在这样的 k,返回 -1。
示例 1:
输入:nums = [2,0,2], queries = [[0,2,1],[0,2,1],[1,1,3]]
输出:2
解释:
- 查询 0 (l = 0, r = 2, val = 1):将索引 [0, 2] 的值减 1,数组变为 [1, 0, 1]
- 查询 1 (l = 0, r = 2, val = 1):将索引 [0, 2] 的值减 1,数组变为 [0, 0, 0],这是零数组
因此最小 k 值为 2。
示例 2:
输入:nums = [4,3,2,1], queries = [[1,3,2],[0,2,1]]
输出:-1
解释:即使执行所有查询也无法使 nums 变成零数组。
示例 3:
输入:nums = [1,2,3,2,1], queries = [[0,1,1],[1,2,1],[2,3,2],[3,4,1],[4,4,1]]
输出:4
约束条件:
1 <= nums.length <= 100 <= nums[i] <= 10001 <= queries.length <= 1000queries[i] = [li, ri, vali]0 <= li <= ri < nums.length1 <= vali <= 10
解题思路
这道题要求找到最少需要执行多少个查询才能将数组变为零数组。关键思路是:
核心思想: 使用二分搜索 + 动态规划验证。对于给定的 k 个查询,需要验证能否将数组变为零数组。
算法步骤:
二分搜索答案: 对查询个数 k 进行二分搜索,范围是 [0, queries.length]
可行性检查: 对于每个可能的 k,检查前 k 个查询是否能将数组变为零数组
动态规划验证: 对于数组的每个位置 i,找出所有能影响该位置的查询(即满足 li ≤ i ≤ ri 的查询),然后使用背包问题的思想,判断这些查询的 val 值能否组合成 nums[i]
背包DP实现: 对于每个位置 i,设 dp[j] 表示是否能用可用查询组合出和为 j。初始化 dp[0] = true,对于每个查询的 val 值,更新 dp 数组
验证结果: 如果对于所有位置 i,都存在 dp[nums[i]] = true,则说明前 k 个查询可以将数组变为零数组
优化要点:
- 由于数组长度最多为 10,可以用位运算优化状态表示
- 每个位置独立验证,降低复杂度
代码实现
class Solution {
public:
int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {
int n = nums.size();
int m = queries.size();
auto canMakeZero = [&](int k) -> bool {
for (int i = 0; i < n; i++) {
if (nums[i] == 0) continue;
vector<int> vals;
for (int j = 0; j < k; j++) {
if (queries[j][0] <= i && i <= queries[j][1]) {
vals.push_back(queries[j][2]);
}
}
vector<bool> dp(nums[i] + 1, false);
dp[0] = true;
for (int val : vals) {
for (int j = nums[i]; j >= val; j--) {
if (dp[j - val]) dp[j] = true;
}
}
if (!dp[nums[i]]) return false;
}
return true;
};
int left = 0, right = m;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canMakeZero(mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
};
class Solution:
def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:
n = len(nums)
m = len(queries)
def can_make_zero(k):
for i in range(n):
if nums[i] == 0:
continue
vals = []
for j in range(k):
if queries[j][0] <= i <= queries[j][1]:
vals.append(queries[j][2])
dp = [False] * (nums[i] + 1)
dp[0] = True
for val in vals:
for j in range(nums[i], val - 1, -1):
if dp[j - val]:
dp[j] = True
if not dp[nums[i]]:
return False
return True
left, right = 0, m
result = -1
while left <= right:
mid = (left + right) // 2
if can_make_zero(mid):
result = mid
right = mid - 1
else:
left = mid + 1
return result
public class Solution {
public int MinZeroArray(int[] nums, int[][] queries) {
int n = nums.Length;
int m = queries.Length;
bool CanMakeZero(int k) {
for (int i = 0; i < n; i++) {
if (nums[i] == 0) continue;
var vals = new List<int>();
for (int j = 0; j < k; j++) {
if (queries[j][0] <= i && i <= queries[j][1]) {
vals.Add(queries[j][2]);
}
}
var dp = new bool[nums[i] + 1];
dp[0] = true;
foreach (int val in vals) {
for (int j = nums[i]; j >= val; j--) {
if (dp[j - val]) dp[j] = true;
}
}
if (!dp[nums[i]]) return false;
}
return true;
}
int left = 0, right = m;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (CanMakeZero(mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}
var minZeroArray = function(nums, queries) {
const n = nums.length;
function canMakeZero(k) {
const arr = [...nums];
for (let i = 0; i < k; i++) {
const [l, r, val] = queries[i];
// For each position in range [l, r], we want to reduce it by val
// But we need to be greedy - reduce the largest values first
const positions = [];
for (let j = l; j <= r; j++) {
if (arr[j] > 0) {
positions.push(j);
}
}
// Sort by value (descending) to be greedy
positions.sort((a, b) => arr[b] - arr[a]);
// Apply reduction of val to each position
for (let j of positions) {
const reduction = Math.min(arr[j], val);
arr[j] -= reduction;
}
}
return arr.every(x => x === 0);
}
let left = 0, right = queries.length;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (canMakeZero(mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(log m × n × k × V),其中 m 是查询数,n 是数组长度,k 是平均每个位置受影响的查询数,V 是数组元素的最大值 |
| 空间复杂度 | O(V),用于动态规划数组 |
相关题目
. Zero Array Transformation I (Medium)
. Zero Array Transformation II (Medium)
. Zero Array Transformation III (Medium)