leetcode - Insert Interval

4 篇文章 0 订阅
3 篇文章 0 订阅

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:
Given intervals [1,3],[6,9], insert and merge [2,5] in as [1,5],[6,9].

Example 2:
Given [1,2],[3,5],[6,7],[8,10],[12,16], insert and merge [4,9] in as [1,2],[3,10],[12,16].

This is because the new interval [4,9] overlaps with [3,5],[6,7],[8,10].


/**
 * Definition for an interval.
 * struct Interval {
 *     int start;
 *     int end;
 *     Interval() : start(0), end(0) {}
 *     Interval(int s, int e) : start(s), end(e) {}
 * };
 */
class Solution {
public:
#define M2
    
#ifdef M1
// O(n2) time, O(1) space, TLE超时
    vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
        vector<Interval>::iterator iter = intervals.begin();
        while(iter != intervals.end()) {
            if (newInterval.end < iter->start) {
                intervals.insert(iter, newInterval);
                return intervals;
            }
            else if (iter->end < newInterval.start) {
                ++iter;
            }
            else {
                newInterval.start = min(newInterval.start, iter->start);
                newInterval.end   = max(newInterval.end, iter->end);
                iter = intervals.erase(iter); // erase will return iterator of next item before iter is removed.
            }
        }
        intervals.insert(intervals.end(), newInterval);
        return intervals;
    }
#endif
#ifdef M2
/*
利用intervals已经排序的特点,采用二分查找,可以在O(lgn)时间内找到起点和终点。
这里的最坏情况还是O(n),即要insert的Interval跨头和尾的位置。
binarySearch:
// 二分查找的变形,将intervals中所有的元素展开(也就是说不分start,end),可以看成一个排序数组。
// low,mid,high和经典二分查找类似;
// mid为在展开的数组中的下标,而idx为该元素在intervals数组的下标;
// mid对应的是intervals[idx]的start还是end呢? 如果mid-2*idx == 1, 则是end,==0,则是start。
// 这个函数有点类似于lower_bound, 它的返回值也可以参考lower_bound来理解。
[ [1,2] ], 0: 返回0
[ [1,5] ], 3: 返回0
[ [3,6] ], 7: 返回1
[ [1,2] [5,8] ], 0/1/2: 返回0
[ [1,2] [5,8] ], 3/4/5/6/7/8: 返回1
[ [1,2] [5,8] ], 9: 返回2
*/
    int binarySearch(vector<Interval> &intervals, int val) {
        int low = 0, high = intervals.size()*2-1;
        while(low <= high) {
            int mid = (low+high)/2; // index in all numbers
            int idx = mid/2; // index in intervals array
            int midv;
            Interval midItem= intervals[idx];
            
            //mid -2*idx == 1 or 0
            midv = (mid-2*idx) ? midItem.end : midItem.start;
            
            if (midv == val) {
                return idx;
            }
            else if (midv < val) {
                low = mid+1;
            }
            else {
                high = mid-1;
            }
        }
        
        return low/2;
    }
    vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
        int pos1 = binarySearch(intervals, newInterval.start);
        int pos2 = binarySearch(intervals, newInterval.end);
        
        // newInterval.start is bigger than all numbers in intervals or intervals is empty
        // [], [1,2] => [ [1,2] ]
        // [ [1,2] ], [3, 4] => [ [1,2] [3,4] ]
        if (pos1 == intervals.size()) {
            intervals.push_back(newInterval);
            return intervals;
        }
        // if newInterval.end is bigger than all numbers in interval, then it will merge with last item.
        // [ [1,3] ], [2, 5] => [ [1,5] ]
        if (pos2 == intervals.size()) {
            pos2 = intervals.size()-1;
        }
        
        // 对于newInterval.start,有两种情况: 
        // newInterval.start < intervals[pos1].start
        // newInterval.start >= intervals[pos1].start
        // 不管哪种情况,都需要将newInterval和intervals[pos1]合并。
        newInterval.start = min(newInterval.start, intervals[pos1].start);
        
        // 对于newInterval.end, 同样有两情况:
        // newInterval.end < intervals[pos2].start
        // newInterval.end >= intervals[pos2].start 
        // 对于第一种情况,我们需要将newInterval和intervals[pos2-1]合并。此时注意要保证pos2-1>=0
        // 对于第二种情况,我们将newInterval和intervals[pos2]合并即;
        if (newInterval.end < intervals[pos2].start) {
            if (pos2-1 >= 0) {
                newInterval.end = max(newInterval.end, intervals[pos2-1].end);
            }
            pos2--;
        }
        else {
            newInterval.end = max(newInterval.end, intervals[pos2].end);
        }
        
        // [ [3,5] ], [1,2] => [ [1,2] [3,5] ], pos1=0, pos2=0-1=-1
        // [ [3,5] [8,9] ], [6,7] => [ [3,5] [6,7] [8,9] ], pos1=1, pos2=1-1=0
        if (pos1 > pos2) {
            intervals.insert(next(intervals.begin(), pos1), newInterval);
        }
        else {
            // [ [2,5] ], [3,4] => [ [2,5] ], pos1=0, pos2=0
            // [ [1,4] [6,8] ], [2,5] => [ [1,5] [6,8] ], pos1=0, pos2=1-1=0
            // [ [1,4] [6,8] ], [2,7] => [ [1,8] ], pos1=0, pos2=1
            typedef vector<Interval>::iterator Iterator;
            Iterator iter1 = next(intervals.begin(), pos1);
            Iterator iter2 = next(intervals.begin(), pos2+1); //[first, last), 所以pos2+1,不然pos2那个位置不会被erase。
            
            iter1 = intervals.erase(iter1, iter2); // 返回iter2之后的位置,这里返回的iter1已经不等于iter2了,因为iter2已经失效了。
            intervals.insert(iter1, newInterval);
        }
        return intervals;
    }
#endif
};
class Solution {
public:
/*
如果用网上的代码,最好的情况是直接插入,不用合并,O(n),
最坏的情况是待插入的Interval横跨所有的Intervals,O(n2),因为erase时需要拷贝数组元素,所以会超时。
原来自己也写过一个用二分查找的方法,但现在觉得那个代码要判断的条件太多了。
这种代码自己平时有时间是可以写出来的,但是需要一些调试才能保证正确性。
于是再次思考,觉得应该有更好的写法,因此有了以下代码。
这个代码思路清晰,简洁,不容易出错,给别人解释也比较容易。
空间复杂度: 这种实现需要O(n)的空间。
时间复杂度:
构造vector<int>, O(n)
binary search: O(logn);
insert: 最坏是在头部插入O(n),最好是在尾部插入,O(1),平均是O(n);
distance: O(1), 注意insert时如果用list插入效率高,但是distance就得O(n)了;
构造vector<Interval> O(n)
所以最坏情况也是O(n)的时间复杂度。
原理:
1. 先用一个int数组保存Interval中的数,由于已经排序,所以可以用binary search。
   理解STL中的lower_bound很重要,经典代码不多说。
2. 找到合适的插入位置之后,分别插入newInterval的start和end,得到他们插入之后的位置。
3. 第一个位置应该在某个Interval的左边,第二个位置则需要在某个Interval的右边,如果不是则需要调整这两个位置。
    在处理第二个位置时,对于相同元素要进一步处理:
    Input:  [[1,5]], [0,1]
    Output: [[0,1],[1,5]]
    Expected:   [[0,5]]
4. 这两个位置之间的元素都需要去掉,其它的元素保留, 所以只需将有效元素重新填到Interval数组中去即可。
*/
    // lower_bound 返回大于等于val的第一个位置
    template <typename FI, typename T>
    FI lower_bound(FI first, FI last, const T &val) {
        while(first < last) {
            FI mid = next(first, distance(first, last)/2);
            if (*mid < val) first = next(mid);
            else last = mid;
        }
        return first;
    }
    vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
        vector<int> v2;
        for(auto i:intervals) {
            v2.push_back(i.start);
            v2.push_back(i.end);
        }
        
        auto pos1 = lower_bound(v2.begin(), v2.end(), newInterval.start); 
        pos1 = v2.insert(pos1, newInterval.start); //  插入后返回所插入元素的实际位置
        int idx1 = distance(v2.begin(), pos1); // !!! 这里必须先算出idx1,不然在下面插入newInterval.end后pos1可能会失效。
        
        // pos2肯定大于pos1,因为newInterval.end >= newInterval.start
        auto pos2 = lower_bound(v2.begin(), v2.end(), newInterval.end);
        pos2 = v2.insert(pos2, newInterval.end);
        int idx2 = distance(v2.begin(), pos2);
        
        if (idx1%2 == 1) idx1--; // 保证是一个Interval的start
        if (idx2%2 == 0) idx2++; // 保证是一个Interval的end
        
        // 进一步合并相同的元素,如[[1,5],插入[0,1]后,[0, 1][1,5] => [0,5]
        if (idx2+1 < v2.size() && v2[idx2] == v2[idx2+1]) idx2+=2;
        
        // 将有效元素填入Interval数组中。
        vector<Interval> result;
        for(int i=0; i<idx1; i+=2) result.push_back({v2[i], v2[i+1]});
        result.push_back({v2[idx1], v2[idx2]});
        for(int i=idx2+1; i<v2.size(); i+=2) result.push_back({v2[i], v2[i+1]});
        
        return result;
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值