引言
在算法问题中,我们经常会遇到需要对数组进行一系列操作以达到某种目标的问题。今天,我们将探讨一个有趣的数组操作问题:给定一个整数数组和一组区间查询,判断是否可以通过一系列减操作将数组所有元素归零。这个问题看似简单,但其中蕴含着一些巧妙的算法思想。让我们循序渐进地分析并解决这个问题。
问题描述
给定:
-
一个长度为
n
的整数数组nums
-
一个二维数组
queries
,其中每个查询queries[i] = [li, ri]
表示一个下标区间
对于每个查询 [li, ri]
,我们可以:
-
在该区间内选择任意一个下标子集(可以选择任意多个下标,也可以不选)
-
将选中的每个下标对应的元素值减 1
我们的目标是:在处理完所有查询后,判断是否可以将 nums
转化为全零数组。
示例分析
为了更好地理解问题,我们先看几个例子:
示例1:
python
nums = [1, 2, 1] queries = [[1, 2], [0, 1]]
操作步骤:
-
第一个查询
[1, 2]
:选择索引1和2,数组变为[1, 1, 0]
-
第二个查询
[0, 1]
:选择索引0和1,数组变为[0, 0, 0]
结果:可以归零,返回True
示例2:
python
nums = [1, 2, 1] queries = [[0, 1], [0, 1]]
操作步骤:
-
第一个查询
[0, 1]
:选择索引0和1,数组变为[0, 1, 1]
-
第二个查询
[0, 1]
:选择索引1,数组变为[0, 0, 1]
结果:无法完全归零,返回False
初步思路
最直观的解法是模拟整个过程:
-
按顺序处理每个查询
-
对于每个查询,尽可能多地减少区间内的元素值
-
最后检查数组是否全零
但这种模拟方法在最坏情况下时间复杂度较高(O(q*n)),对于大规模数据可能不够高效。我们需要寻找更聪明的解法。
关键观察
通过分析,我们发现一个重要性质:
-
对于每个元素
nums[i]
,它必须被至少nums[i]
个包含它的查询区间覆盖 -
因为每次查询只能减少最多1(如果选择该元素)
换句话说:
-
统计每个索引被多少查询区间覆盖
-
对于每个
i
,必须满足nums[i] <= coverage[i]
(coverage[i]
是索引i被覆盖的次数)
优化思路
为了高效计算每个索引被覆盖的次数,我们可以使用差分数组技术:
-
初始化一个差分数组
diff
(长度n+1) -
对于每个查询
[li, ri]
:-
diff[li] += 1
-
diff[ri+1] -= 1
(如果ri+1在范围内)
-
-
通过差分数组计算前缀和得到
coverage
数组 -
比较
nums
和coverage
数组
这种方法将时间复杂度优化到O(n+q),空间复杂度O(n)。
完整解决方案
以下是Java实现代码:
java
public boolean isPossibleToZero(int[] nums, int[][] queries) { int n = nums.length; int[] diff = new int[n + 1]; // 构建差分数组 for (int[] query : queries) { int li = query[0]; int ri = query[1]; diff[li]++; if (ri + 1 < n) { diff[ri + 1]--; } } // 计算覆盖次数 int[] coverage = new int[n]; coverage[0] = diff[0]; for (int i = 1; i < n; i++) { coverage[i] = coverage[i - 1] + diff[i]; } // 检查是否满足条件 for (int i = 0; i < n; i++) { if (nums[i] > coverage[i]) { return false; } } return true; }
复杂度分析
-
时间复杂度:O(n + q),其中n是数组长度,q是查询数量
-
空间复杂度:O(n),用于存储差分数组和覆盖次数数组
测试用例
java
public static void main(String[] args) { Solution solution = new Solution(); // 示例1 int[] nums1 = {1, 2, 1}; int[][] queries1 = {{1, 2}, {0, 1}}; System.out.println(solution.isPossibleToZero(nums1, queries1)); // true // 示例2 int[] nums2 = {1, 2, 1}; int[][] queries2 = {{0, 1}, {0, 1}}; System.out.println(solution.isPossibleToZero(nums2, queries2)); // false // 全零数组 int[] nums3 = {0, 0, 0}; int[][] queries3 = {{0, 1}, {1, 2}}; System.out.println(solution.isPossibleToZero(nums3, queries3)); // true // 单个元素多次查询 int[] nums4 = {3}; int[][] queries4 = {{0, 0}, {0, 0}, {0, 0}}; System.out.println(solution.isPossibleToZero(nums4, queries4)); // true }
总结
通过这个问题,我们学到了:
-
如何将操作问题转化为覆盖统计问题
-
差分数组技术在区间更新问题中的应用
-
算法设计中的关键观察和转化思维
希望这篇文章能帮助你理解这个有趣的算法问题。如果有任何疑问或建议,欢迎留言讨论!