加载中...
1893-检查是否区域内所有整数都被覆盖(Check if All the Integers in a Range Are Covered)
发表于:2021-12-03 | 分类: 简单
字数统计: 277 | 阅读时长: 1分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/check-if-all-the-integers-in-a-range-are-covered

英文原文

You are given a 2D integer array ranges and two integers left and right. Each ranges[i] = [starti, endi] represents an inclusive interval between starti and endi.

Return true if each integer in the inclusive range [left, right] is covered by at least one interval in ranges. Return false otherwise.

An integer x is covered by an interval ranges[i] = [starti, endi] if starti <= x <= endi.

 

Example 1:

Input: ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5
Output: true
Explanation: Every integer between 2 and 5 is covered:
- 2 is covered by the first range.
- 3 and 4 are covered by the second range.
- 5 is covered by the third range.

Example 2:

Input: ranges = [[1,10],[10,20]], left = 21, right = 21
Output: false
Explanation: 21 is not covered by any range.

 

Constraints:

  • 1 <= ranges.length <= 50
  • 1 <= starti <= endi <= 50
  • 1 <= left <= right <= 50

中文题目

给你一个二维整数数组 ranges 和两个整数 left 和 right 。每个 ranges[i] = [starti, endi] 表示一个从 starti 到 endi 的 闭区间 。

如果闭区间 [left, right] 内每个整数都被 ranges 中 至少一个 区间覆盖,那么请你返回 true ,否则返回 false 。

已知区间 ranges[i] = [starti, endi] ,如果整数 x 满足 starti <= x <= endi ,那么我们称整数x 被覆盖了。

 

示例 1:

输入:ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5
输出:true
解释:2 到 5 的每个整数都被覆盖了:
- 2 被第一个区间覆盖。
- 3 和 4 被第二个区间覆盖。
- 5 被第三个区间覆盖。

示例 2:

输入:ranges = [[1,10],[10,20]], left = 21, right = 21
输出:false
解释:21 没有被任何一个区间覆盖。

 

提示:

  • 1 <= ranges.length <= 50
  • 1 <= starti <= endi <= 50
  • 1 <= left <= right <= 50

通过代码

高赞题解

解题思路

image.png

当然这个还可以利用堆,排序,hash,线段树,位运算等等很多。
接下来介绍四种参考
但是差分数组思想是最强的,功能不止于此。
暴力和优化版本标记数组只需要布尔型即可。

因为以下解法申请空间在52长度内,空间复杂度O(1),而且第二层for循环都是对52空间内修改,故时间复杂度为O(n),.

解法1:暴力

在每个节点起始位置和终点位置之间的flag标记为true,代表该点已经被包含。
当我们查询时,如果查询left与right之间只要有一点为false,返回flase

class Solution {
    public boolean isCovered(int[][] ranges, int left, int right) {
        boolean[] flag = new boolean[51];
        for(int[] range : ranges){
            for(int i = range[0]; i <= range[1];i++){
                flag[i] = true;
            }
        }
        for(int i = left; i <= right; i++){
            if(flag[i] == false) return false;
        }
        return true;
    }
}

暴力的时间复杂度是O(51n) = O(n),同理空间复杂度O(1)。

方法2:基于排序

如将区间的起始点从小到达排序,然后每次比较,如果拿到的区间[l,r],left在区间内,即 l <= left <= r,那么可知,[left,r]便已经被覆盖,接下来只需接续检查剩余空白部分,让left = r + 1, 如果最后left可以超过right,则区间全部被覆盖。为true。

class Solution {
    public boolean isCovered(int[][] ranges, int left, int right) {
        Arrays.sort(ranges, (a1, a2) -> a1[0] - a2[0]);
        for(int[] range: ranges) {
            int l = range[0];
            int r = range[1];
            if(l <= left && left <= r) {
                left = r + 1;
            }
        }
        return left > right;
    }
}

时间复杂度为排序算法的复杂度度 O(nlog(n))。空间复杂度,由于java7之后数组排序采用DualPrivotQuicksort代替原来快排,规模小时为插入排序,规模大时为快速排序,快速排序中,性能较为优越稳定的是随机化快排,比其他挖坑填数,取中值,或者基础快排正常情况下是2倍到4倍,属于越大越明显。所以空间复杂度O(logn)。

但是我们还可以继续优化

解法3:优化 只标记[left,right]

因为我们是查询left与right之间的点是否完全包含在原数组表示区间中,那么我们只需要关心
[left,right]上这一段即可。我们只标记[left,right]区间即可,
以[left,right]为基准思考,每当从二维数组中拿到一个区间[l,r],
我们只需要标记[left,right]与[l,r]的交集部分。如果没有交集不标记。
如果l比left还小,我们没必要从l开始标记,从left标记。
如果r比right还大,我们没必要标记到r,只要标记到right即可。
故只需要定义两个变量:
L = Math.max(l,left);
R = Math.min(r,right);
看下图理解:
拿到的区间如果长度小,变为以下五种情况, 那么L,R满足 L <= R,才能标记,否则
不能标记,如果拿到区间长,同理,这里只是画一下示意。

L 为left,l的较大值, R 为right,r的较小值 打钩为可标记

这便是取交集思想

image.png
让拿到的区间,从左到右相对于[left,right] 进行滑动即可。

class Solution {
    public boolean isCovered(int[][] ranges, int left, int right) {
        boolean[] flag = new boolean[51];
        for(int[] range : ranges){
            int L = Math.max(range[0],left);
            int R = Math.min(range[1],right);
            for(int i = L; i <= R; i++){
                flag[i] = true;
            }
        }
        for(int i = left; i <= right; i++){
            if(flag[i] == false) return false;
        }
        return true;
    }
}

因为遍历过程中,我们需要对L,R之间进行修改,但是[L,R]区间是小于或等于[left,right]区间的,
每次在区间内做修改,故时间复杂度为O(n(left - right + 1))也等同O(n),因为宽度最大为常数51
故时间复杂度 O(n)。
空间复杂度O(1)。

解法4:差分数组

差分数组diff表示相邻格之间,是否被覆盖的变化量。
diff[i]++,代表在i位置上有新的覆盖
若覆盖到j结束了呢?此时j依然是覆盖,但是j+1不在覆盖状态,所以在j+1处 -1;
即diff[j+1]–;
当我们把差分数组求前缀和,就很直观把这种变化量转化为不变的,可以理解的。
前缀和sum[i]的大小,就代表被覆盖次数,接下来用图展示;

如有四个需要加入的区间 1,2,3,4;加入之后 all为加入之后区间图覆盖图,但是看不出了每个点被覆盖几次,但是差分前缀和可以做到,接下来继续了解。

image.png

我们定义diff差分数组,由上定义可知,在起始下标+1,在终止下标之后-1;当加入(1)和(2)之后,差分数组黄色区域为被覆盖部分,但是从差分数组看不出来,只是示意标出来,

image.png

接下来继续同理加入(3) 和 (4);由于(4)的是最后结束,-1没有位置,所以差分数组定义时候必须多定义一个,int[52]

image.png

接下来就是最关键,最熟悉对差分数组求前缀和。sum,发现在覆盖的地方,都是正数,而没有覆盖的地方为0,如果要查询 [2,5],直接将[2,5]区间与sum逐个对比,看sun是否大于0,如果发现不大于0,一定出现了空白,没有覆盖,

image.png

而我们从前缀和数组可以看出,每一个位大小代表被覆盖几次,比如[1,2]区域都是2,因为我们在加入时候 (1)和(2)正是在这个地方重复覆盖,

所以差分数组前缀和不仅能查询是否被覆盖,还能查询某一区间被覆盖几次。

class Solution {
    public boolean isCovered(int[][] ranges, int left, int right) {
        int[] diff = new int[52];
        //对差分数组进行处理
        for(int i = 0; i < ranges.length; i++){
            diff[ranges[i][0]]++;
            diff[ranges[i][1]+1]--;
        }
        //根据差分数组处理前缀和,为理解方便单独定义sum,可以原地做
        int[] sum = new int[52];
        for(int i = 1; i <= 51; i++){
            sum[i] = sum[i-1] + diff[i];
        }
        //从left到right判断是否满足sum > 0
        for(int i = left; i <= right; i++){
            if(sum[i] <= 0) return false;
        }
        return true;
    }
}

差分算法的时间复杂度为O(n),空间复杂度为O(1),
上述代码是为了方便理解。
简化版可以把前缀和与diff和在一起,就原地做,判断也加一起。

理解小伙伴点个赞,向各位大佬学习!

统计信息

通过次数 提交次数 AC比率
32128 54395 59.1%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
1872-石子游戏 VIII(Stone Game VIII)
下一篇:
1894-找到需要补充粉笔的学生编号(Find the Student that Will Replace the Chalk)
本文目录
本文目录