如何判断通过区间减操作能否将数组归零

引言

在算法问题中,我们经常会遇到需要对数组进行一系列操作以达到某种目标的问题。今天,我们将探讨一个有趣的数组操作问题:给定一个整数数组和一组区间查询,判断是否可以通过一系列减操作将数组所有元素归零。这个问题看似简单,但其中蕴含着一些巧妙的算法思想。让我们循序渐进地分析并解决这个问题。

问题描述

给定:

  • 一个长度为 n 的整数数组 nums

  • 一个二维数组 queries,其中每个查询 queries[i] = [li, ri] 表示一个下标区间

对于每个查询 [li, ri],我们可以:

  1. 在该区间内选择任意一个下标子集(可以选择任意多个下标,也可以不选)

  2. 将选中的每个下标对应的元素值减 1

我们的目标是:在处理完所有查询后,判断是否可以将 nums 转化为全零数组

示例分析

为了更好地理解问题,我们先看几个例子:

示例1

python

nums = [1, 2, 1]
queries = [[1, 2], [0, 1]]

操作步骤:

  1. 第一个查询 [1, 2]:选择索引1和2,数组变为 [1, 1, 0]

  2. 第二个查询 [0, 1]:选择索引0和1,数组变为 [0, 0, 0]
    结果:可以归零,返回 True

示例2

python

nums = [1, 2, 1]
queries = [[0, 1], [0, 1]]

操作步骤:

  1. 第一个查询 [0, 1]:选择索引0和1,数组变为 [0, 1, 1]

  2. 第二个查询 [0, 1]:选择索引1,数组变为 [0, 0, 1]
    结果:无法完全归零,返回 False

初步思路

最直观的解法是模拟整个过程:

  1. 按顺序处理每个查询

  2. 对于每个查询,尽可能多地减少区间内的元素值

  3. 最后检查数组是否全零

但这种模拟方法在最坏情况下时间复杂度较高(O(q*n)),对于大规模数据可能不够高效。我们需要寻找更聪明的解法。

关键观察

通过分析,我们发现一个重要性质:

  • 对于每个元素 nums[i],它必须被至少 nums[i] 个包含它的查询区间覆盖

  • 因为每次查询只能减少最多1(如果选择该元素)

换句话说:

  • 统计每个索引被多少查询区间覆盖

  • 对于每个 i,必须满足 nums[i] <= coverage[i]coverage[i] 是索引i被覆盖的次数)

优化思路

为了高效计算每个索引被覆盖的次数,我们可以使用差分数组技术:

  1. 初始化一个差分数组 diff(长度n+1)

  2. 对于每个查询 [li, ri]

    • diff[li] += 1

    • diff[ri+1] -= 1(如果ri+1在范围内)

  3. 通过差分数组计算前缀和得到 coverage 数组

  4. 比较 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
}

总结

通过这个问题,我们学到了:

  1. 如何将操作问题转化为覆盖统计问题

  2. 差分数组技术在区间更新问题中的应用

  3. 算法设计中的关键观察和转化思维

希望这篇文章能帮助你理解这个有趣的算法问题。如果有任何疑问或建议,欢迎留言讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值