LeetCode #57 Insert Interval

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SquirrelYuyu/article/details/83190012

文章目录


(Week 6 算法博客)
Insert Interval

题目

Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary).

You may assume that the intervals were initially sorted according to their start times.

Example 1:

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]

Example 2:

Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]

Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].

Difficulty: Hard

分析

这道题,乍看可以暴力求解,直接遍历一遍数组,找到合适的开始区间和结束区间。时间复杂度是 O ( n ) O(n)

但是考虑到区间的有序性,我们可以进行二分查找,将时间复杂度缩小到 O ( l o g n ) O(logn)

我们要考虑到起点/终点位置的各种可能性:

  • 起点/终点处于某个区间内

  • 起点/终点处于两个区间之间

  • 起点/终点处于第一个区间之前

  • 起点/终点处于最后一个区间之后

这样,会组合出不同的情况。

为了方便表示起点/终点的位置,我们设两个变量 startItvendItv ,它们都是某个区间在区间数组 intervals 里的索引。初始值为 0size - 1

进行二分查找,每次找 startItvendItv 的中间区间 medium ,通过比较点和 medium 两端的值,设置新的 startItvendItv

查找完后:

  • 如果点在第一个区间之前,会有 startItv == -1

  • 如果点在最后一个区间之后,会有 startItv == size - 1endItv == size

  • startItv == endItv ,表示点在 intervals[startItv] 这个区间内

  • startItv != endItv ,表示点在 intervals[startItv]intervals[endItv] 这两个区间之间

有进行查找、求 startItvendItv 的函数如下:

    void find(const vector<Interval>& intervals, int num, int& startItv, int& endItv){
    	startItv = 0;
    	endItv = intervals.size() - 1;
    	int result;
    	while(1){
    		int size = endItv - startItv + 1;
    		int medium = startItv + size / 2;
    		const Interval& itv1 = intervals[medium];
    		if(size == 1){
    			if(num < itv1.start) startItv--;
    			else if(itv1.end < num) endItv++;
    			break;
			}
    		const Interval& itv2 = intervals[medium - 1];
    		if(itv1.start <= num) startItv = medium;				// 点在 intervals[medium] 之内或之后
			else if(num <= itv2.end) endItv = medium - 1;	// 点在 intervals[medium - 1] 之内或之前
			else{					// 点在 intervals[medium - 1] 和 intervals[medium] 之间
				startItv = medium - 1;
				endItv = medium;
				break;
			} 
			// cout << num << " : " << startItv << " " << endItv << endl;
		}
	}

接下来,需要由起点 A 和终点 BstartItvendItv 来判断,怎么创造新的区间。

起点要插入的开始区间,由起点的 endItv 确定;终点要插入的结束区间,由终点的 startItv 确定。

startItvendItv 是否相等,决定是否将开始区间的起点/结束区间的终点替换成 A / B

可以直接将开始区间和结束区间合并,除了以下几种特殊情况:

① 终点的 startItv == -1 ,则要在原来的区间数组之前插入新区间。

② 起点的插入区间的索引等于 size ,则要在原来的区间数组之后插入新区间。

③ 起点和终点位于同样的两个区间之间,在这种情况下,起点的 endItv 和终点的 startItv 顺序会相反。即是说,确定的开始区间在结束区间之后。

可以看出,①②的判断条件,本质上也是起点的 endItv 和终点的 startItv 顺序相反。

对①②③,都只需要将新区间插入区间数组的,起点的 endItv 处。

最终,进行插入操作的函数的代码如下:

    vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) {
    	if(intervals.size() == 0){
    		intervals.push_back(newInterval);
    		return intervals;
		}
        int A = newInterval.start, B = newInterval.end;
        int startItvA, endItvA;
        find(intervals, A, startItvA, endItvA);
        int startItvB, endItvB;
        find(intervals, B, startItvB, endItvB);
        
        int insertA;	// 表示起点要插入的开始区间
        int insertB;	// 表示终点要插入的结束区间
        insertA = endItvA;
		insertB = startItvB;
		
		if(insertB < insertA){ 
			intervals.insert(intervals.begin() + insertA, Interval(A, B));
			return intervals;
		}
        
		if(startItvA != endItvA) intervals[insertA].start = A;
		if(startItvB != endItvB) intervals[insertB].end = B;
		intervals[insertA].end = intervals[insertB].end;
		intervals.erase(intervals.begin() + insertA + 1, intervals.begin() + insertB + 1);
		return intervals;
    }

提供几组测试数据,方便后来人:

区间数组 1 :[[1,4][6,12][16,25]]
要插入的新区间:
[-7,0]
[-2,14]
[-6,23]
[-5,31]
[2,14]
[7,23]
[7,33]
[3,15]
[7,23]
[13,33]
[30,39]
[5,5]

区间数组 2 :[]
要插入的新区间:
[-7,0]

结语

这道题虽然难度是 Hard ,提交率也是比较低的 29.9% ,但其实,输入的区间数组的有序性使得它的核心算法很简单,只是插入区间需要考虑各种特殊情况,这可能是提交率低的原因。大概是一道假的难题了。

猜你喜欢

转载自blog.csdn.net/SquirrelYuyu/article/details/83190012