区间重叠问题(排序or边界)

1.会议室(252-easy)

题目描述:给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,请你判断一个人是否能够参加这里面的全部会议,即判断是否存在重叠区域!

示例

输入: intervals = [[0,30],[5,10],[15,20]]
输出: false
解释: 存在重叠区间,一个人在同一时刻只能参加一个会议。

思路:对每个会议时间按照开始时间排序(关键)。然后遍历数组进行判断,如果前一个会议结束的时间大于后一个会议开始的时间(前一个还没结束,后一个就开始了),则存在重叠区域。

代码

public boolean canAttendMeetings(int[][] intervals) {
    if (intervals == null || intervals.length == 0) return false;
    /**       
    Arrays.sort(intervals, new Comparator<int[]>() {

            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] - o2[0];
            }
        });
  */
    Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
    for (int i = 1; i < intervals.length; i++) {
        // 前一个结束时间大于后一个开始时间
        if (intervals[i][0] < intervals[i - 1][1]) {
            returtn false;
        }
    }
    return true;
}

拓展1:会议室II 253,给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],…] (si < ei),为避免会议冲突,同时要考虑充分利用会议室资源,请你计算至少需要多少间会议室,才能满足这些会议安排。

示例

示例 1:
输入: [[0, 30],[5, 10],[15, 20]]
输出: 2

示例 2:
输入: [[7,10],[2,4]]
输出: 1

思路:有时间重叠的,肯定不能安排在一间会议室。还是需要先排序,这里按照会议的开始时间排序,这里使用小根堆(优先级队列)存储会议的结束时间(堆顶为会议最早结束时间):

  • 如果另一场会议的开始时间小于当前堆顶,说明会议时间冲突,我们需要再单独开一间(即当前会议的结束时间作为新的堆顶);
  • 否则,没有时间冲突,等这场会议结束使用。

代码

import java.util.LinkedList;
class Solution {
    public int minMeetingRooms(int[][] intervals) {
        if(intervals == null || intervals.length == 0){
            return 0;
        }
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o1 - o2);
        queue.offer(intervals[0][1]);
        for(int i = 1; i < intervals.length; i++){
            if(intervals[i][0] >= queue.peek()){
                queue.poll();
            }
            queue.offer(intervals[i][1]);
        }
        return queue.size();
    }
}

拓展2:最多可以参加的会议数目1353,给你一个数组 events,其中 events[i] = [startDayi, endDayi] ,表示会议 i 开始于 startDayi ,结束于 endDayi 。

你可以在满足 startDayi <= d <= endDayi 中的任意一天 d 参加会议 i 。注意,一天只能参加一个会议。请你返回你可以参加的 最大 会议数目。

示例

示例 1:
输入:events = [[1,2],[2,3],[3,4]]
输出:3
解释:你可以参加所有的三个会议。
安排会议的一种方案如上图。
第 1 天参加第一个会议。
第 2 天参加第二个会议。
第 3 天参加第三个会议。

思路:本题还是排序,注意题意,我们不是要把一个会议的所有天都在,只需要参加满足会议(在开始和结束之间参加)的一天即可,比较简单的是利用set存储已经占用的天数(因为同一天不能参加两个会议),但是超时。。。

可以使用优先级队列,将这一天能参加的会议的结束时间全部入堆,会议的起始时间 == day,这一天一定能参加(注意按照会议的开始时间排序)。为什么是结束时间?有点贪心思想,保证能参加最多的会议

  • 我们一天一天安排,首先删除已经结束的会议(出堆),此时堆顶这一天是我们可以参加的会议(一天只能参加一场)!

代码

class Solution {
    // public int maxEvents(int[][] events) {
    //     Arrays.sort(events, (o1, o2) -> o1[1] - o2[1]);
    //     Set<Integer> set = new HashSet<>();
    //     for (int[] event : events) {
    //         for (int i = event[0]; i <= event[1]; ++i) {
    //             if (!set.contains(i)) {
    //                 set.add(i);
    //                 break;
    //             }
    //         }
    //     }
    //     return set.size();
    // }

    public int maxEvents(int[][] events) {
        Arrays.sort(events, (o1, o2) -> o1[0] - o2[0]);
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        int i = 0;
        int day = 1;
        int ans = 0;
        int n = events.length;

        while (i < n || !pq.isEmpty()) {
            while (i < n && events[i][0] == day) {
                pq.offer(events[i][1]);
                i++;
            }

            while (!pq.isEmpty() && pq.peek() < day) {
                pq.poll();
            }

            if (!pq.isEmpty()) {
                pq.poll();
                ans++;
            }
            day++;
        }
        return ans;
    }
}

2.不重叠区间(435-medium)

题目描述:给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

示例

输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

思路:为了保证移除最少的区间集合,即寻找最多的不重叠区间贪心策略:保证每个区间尾越小,那么后边预留的空间越大。

  • 可以通过对尾区间进行排序,遍历数组(记录不重叠个数count),当前头小于上一个的尾,直接删除(重叠),
  • 否则count++,更新最小的尾区间end。

ps:注意起始边界处理,时间复杂度:O(nlogn)!

代码

public int eraseOverlapIntervals(int[][] intervals) {
    if (intervals == null || intervals.length == 0) return 0;
    Arrays.sort(intervals, (a, b) -> a[1] - b[1]); //1.每个子区间尾排序
    int count = 1;
    int end = intervals[0][1]; 
    
    for (int i = 1; i < intervals.length; i++) {
        if (intervals[i][0] < end) continue;    //2.与之前区间重叠(即当前区间的头小于之前区间的尾,删除)
        end = intervals[i][1];
        count++;
    }
    return intervals.length - count;    //3.要删除的区间数量
}

3.合并区间(56-medium)

题目描述:给出一个区间的集合,请合并所有重叠的区间。

示例

输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:实现合并区间肯定要进行排序。采用每个子区间左边界排序(右边界也可),然后从左向右遍历数组。注意:

  • 若没有重叠(数组为空/当前区间的左边界,大于结果区间的右边界),不需合并则加入结果;
  • 否则更新右边界生成新的区间加入结果,更新结果区间的右边界。

代码

public int[][] merge(int[][] intervals) {
        //1.按照每个子区间端点(左端点)进行排序
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
        int[][] ret = new int[intervals.length][2];
        int idx = -1;

        for (int[] interval : intervals) {
            //3.没有重叠(数组为空/当前区间左边界大于结果区间右边界),直接加入
            if (idx == -1 || interval[0] > ret[idx][1]) { 
                ret[++idx] = interval;
            } else {
                //4.有重叠,合并区间(更新结果区间右边界)
                ret[idx][1] = Math.max(interval[1], ret[idx][1]);  
            }
        }
        //ps:copyOf(int[] original, int newLength) 
        return Arrays.copyOf(ret, idx + 1);
    }

4.插入区间(57-medium)

题目描述:给出一个无重叠的按照区间起始端点排序的区间列表。在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

示例

输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]

输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。

思路:因为原始数组已经排好序,并且不存在重叠,那么直接用索引i遍历数组,索引idx记录结果集索引,分三步走

  • 如果存在,将左边与新区间不重叠的部分直接接入结果(没影响的部分);
  • 新区间与区间存在重叠,合并区间(更新新区间左右边界),将更新后的新区间加入结果;通俗一点说:新区间的左边界<= 当前上一个区间的右边界,>= 下一个区间的左边界,我们需要将这三个区间合并(更新新区间的左右边界,加入结果)
  • 如果存在,将右边与新区间不重叠的部分直接接入结果(没影响的部分);

代码

public int[][] insert(int[][] intervals, int[] newInterval) {
    int[][] ret = new int[intervals.length + 1][2];
    int idx = 0, i = 0;
    //1.左边不重叠区间
    while (i < intervals.length && newInterval[0] > intervals[i][1]) {
        ret[idx++] = intervals[i++];
    }
    //2.区间合并(更新新区间的左右边界)
    while (i < intervals.length && newInterval[1] >= intervals[i][0]) {
        newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
        newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
        i++;
    }
    ret[idx++] = newInterval;
    //3.右边不重叠区间
    while (i < intervals.length) {
        ret[idx++] = intervals[i++];
    }
    return Arrays.copyOf(ret, idx);
}

总结

总结上述四个题目,主要可以细分为两类问题。

涉及区间重叠问题:

  • 区间是否重叠(T252)
  • 最多的的不重叠区间(T435)

涉及区间合并问题:

  • 合并所有重叠区间(T56)
  • 合并插入过程中可能引入的重叠区间(T57)

其实,上述问题都需要对区间端点进行排序,这里明确一点,不管是根据左端点排序还是右端点排序都是可以的。需要画图去看左右边界情况,比较直观。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容