1288. 删除被覆盖区间

区间问题小结 - 图1
题目问我们,去除被覆盖区间之后,还剩下多少区间,那么我们可以先算一算,被覆盖区间有多少个,然后和总数相减就是剩余区间数
还是先排序:按照开始Start排序:
image.png
排序后相邻的两个区间有三种状态:
image.png
对于这三种情况,我们应该这样处理:
对于情况一,找到了覆盖区间。
对于情况二,两个区间可以合并,成一个大区间。
对于情况三,两个区间完全不相交。
依据几种情况,我们可以写出如下代码:

  1. int removeCoveredIntervals(int[][] intvs) {
  2. // 按照起点升序排列,起点相同时降序排列
  3. Arrays.sort(intvs, (a, b) -> {
  4. if (a[0] == b[0]) {
  5. return b[1] - a[1];
  6. }
  7. return a[0] - b[0];
  8. });
  9. // 记录合并区间的起点和终点
  10. int left = intvs[0][0];
  11. int right = intvs[0][1];
  12. int res = 0;
  13. for (int i = 1; i < intvs.length; i++) {
  14. int[] intv = intvs[i];
  15. // 情况一,找到覆盖区间
  16. if (left <= intv[0] && right >= intv[1]) {
  17. res++;
  18. }
  19. // 情况二,找到相交区间,合并
  20. if (right >= intv[0] && right <= intv[1]) {
  21. right = intv[1];
  22. }
  23. // 情况三,完全不相交,更新起点和终点
  24. if (right < intv[0]) {
  25. left = intv[0];
  26. right = intv[1];
  27. }
  28. }
  29. return intvs.length - res;
  30. }

起点升序排列,终点降序排列的目的是防止如下情况
image.png

56. 合并区间

leetcode
区间问题小结 - 图5
一般思路是先排序 选择按start排序。
image.png

  1. class Solution {
  2. public int[][] merge(int[][] intervals) {
  3. LinkedList<int[]> res = new LinkedList<>();
  4. //按区间的 start 升序排列
  5. Arrays.sort(intervals,(a,b)->{
  6. return a[0]-b[0];
  7. });
  8. res.add(intervals[0]);
  9. for (int i = 1; i < intervals.length; i++) {
  10. int[] curr = intervals[i];
  11. // res 中最后一个元素的引用
  12. int[] last = res.getLast();
  13. if (curr[0] <= last[1]){//只需要比较右边即可,因为我们已经排序
  14. last[1] = Math.max(curr[1],last[1]);//更新数组边界
  15. }else {
  16. // 处理下一个待合并区间
  17. res.add(curr);
  18. }
  19. }
  20. return res.toArray(new int[0][0]);
  21. }
  22. }

区间问题小结 - 图7

986. 区间列表的交集

详细参考了labuladong
区间问题小结 - 图8

  1. class Solution {
  2. public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
  3. LinkedList<int[]> res = new LinkedList<>();
  4. //两个指针
  5. int i = 0;
  6. int j = 0;
  7. while (i< firstList.length && j < secondList.length){
  8. int a1 = firstList[i][0], a2 = firstList[i][1];
  9. int b1 = secondList[j][0], b2 = secondList[j][1];
  10. if (b2 >= a1 && a2 >= b1){//有交集的情况
  11. //计算出交集,加入 res
  12. res.add(new int[]{
  13. Math.max(a1, b1), Math.min(a2, b2)
  14. });
  15. }
  16. if (b2 < a2) {
  17. j++;
  18. } else {
  19. i++;
  20. }
  21. }
  22. return res.toArray(new int[0][0]);
  23. }
  24. }

1024. 视频拼接

区间问题小结 - 图9
区间问题肯定按照区间的起点或者终点进行排序

先按照起点升序排序,如果起点相同的话按照终点降序排序。
原因:
1、要用若干短视频凑出完成视频[0, T],至少得有一个短视频的起点是 0。
这个很好理解,如果没有一个短视频是从 0 开始的,那么区间[0, T]肯定是凑不出来的。
2、如果有几个短视频的起点都相同,那么一定应该选择那个最长(终点最大)的视频。

基于以上两个特点,将clips按照起点升序排序,起点相同的按照终点降序排序,最后得到的区间顺序就像这样:
区间问题小结 - 图10
这样我们就可以确定,如果clips[0]是的起点是 0,那么clips[0]这个视频一定会被选择。

区间问题小结 - 图11
当我们确定clips[0]一定会被选择之后,就可以选出第二个会被选择的视频:区间问题小结 - 图12
我们会比较所有起点小于clips[0][1]的区间,根据贪心策略,它们中终点最大的那个区间就是第二个会被选中的视频
然后可以通过第二个视频区间贪心选择出第三个视频,以此类推,直到覆盖区间[0, T],或者无法覆盖返回 -1。
为了实现我们需要两个变量
区间问题小结 - 图13

class Solution {
    public int videoStitching(int[][] clips, int T) {
        if (T == 0) return 0;
        // 按起点升序排列,起点相同的降序排列
        // PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰
        Arrays.sort(clips, (a, b) -> {
            if (a[0] == b[0]) {
                return b[1] - a[1];
            }
            return a[0] - b[0];
        });
        // 记录选择的短视频个数
        int res = 0;

        int curEnd = 0, nextEnd = 0;
        int i = 0, n = clips.length;
        while (i < n && clips[i][0] <= curEnd) {
            // 在第 res 个视频的区间内贪心选择下一个视频
            while (i < n && clips[i][0] <= curEnd) {
                nextEnd = Math.max(nextEnd, clips[i][1]);
                i++;
            }
            // 找到下一个视频,更新 curEnd
            res++;
            curEnd = nextEnd;
            if (curEnd >= T) {
                // 已经可以拼出区间 [0, T]
                return res;
            }
        }
        // 无法连续拼出区间 [0, T]
        return -1;
    }
}
// 详细解析参见:
// https://labuladong.github.io/article/?qno=1024