区间调度问题之区间合并.md 7.8 KB
Newer Older
L
labuladong 已提交
1 2
# 区间调度问题之区间合并

3 4 5 6 7 8 9 10 11 12

<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>

![](../pictures/souyisou.png)

L
labuladong 已提交
13
**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~
14 15 16 17 18 19 20

读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:

[56.合并区间](https://leetcode-cn.com/problems/merge-intervals)

**-----------**

L
labuladong 已提交
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
上篇文章用贪心算法解决了区间调度问题:给你很多区间,让你求其中的最大不重叠子集。

其实对于区间相关的问题,还有很多其他类型,本文就来讲讲区间合并问题(Merge Interval)。

LeetCode 第 56 题就是一道相关问题,题目很好理解:

![title](../pictures/mergeInterval/title.png)

我们解决区间问题的一般思路是先排序,然后观察规律。

### 一、思路

一个区间可以表示为 `[start, end]`,前文聊的区间调度问题,需要按 `end` 排序,以便满足贪心选择性质。而对于区间合并问题,其实按 `end``start` 排序都可以,不过为了清晰起见,我们选择按 `start` 排序。

![1](../pictures/mergeInterval/1.jpg)

**显然,对于几个相交区间合并后的结果区间 `x`,`x.start` 一定是这些相交区间中 `start` 最小的,`x.end` 一定是这些相交区间中 `end` 最大的。**

![2](../pictures/mergeInterval/2.jpg)

由于已经排了序,`x.start` 很好确定,求 `x.end` 也很容易,可以类比在数组中找最大值的过程:

```java
int max_ele = arr[0];
for (int i = 1; i < arr.length; i++) 
    max_ele = max(max_ele, arr[i]);
return max_ele;
```

### 二、代码

```python
# intervals 形如 [[1,3],[2,6]...]
def merge(intervals):
    if not intervals: return []
    # 按区间的 start 升序排列
    intervals.sort(key=lambda intv: intv[0])
    res = []
    res.append(intervals[0])
    
    for i in range(1, len(intervals)):
        curr = intervals[i]
        # res 中最后一个元素的引用
        last = res[-1]
        if curr[0] <= last[1]:
            # 找到最大的 end
            last[1] = max(last[1], curr[1])
        else:
            # 处理下一个待合并区间
            res.append(curr)
    return res
```

看下动画就一目了然了:

![3](../pictures/mergeInterval/3.gif)

至此,区间合并问题就解决了。本文篇幅短小,因为区间合并只是区间问题的一个类型,后续还有一些区间问题。本想把所有问题类型都总结在一篇文章,但有读者反应,长文只会收藏不会看... 所以还是分成小短文吧,读者有什么看法可以在留言板留言交流。

L
labuladong 已提交
80 81
本文终,希望对你有帮助。

82
**_____________**
L
labuladong 已提交
83

L
labuladong 已提交
84
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**
L
labuladong 已提交
85

86
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
L
labuladong 已提交
87

88
<p align='center'>
L
labuladong 已提交
89
<img src="../pictures/qrcode.jpg" width=200 >
90
</p>
K
Kian Kwok 已提交
91 92
======其他语言代码======

B
brucecat 已提交
93 94 95 96
[56.合并区间](https://leetcode-cn.com/problems/merge-intervals)



B
BruceCat 已提交
97 98 99
### java

```java
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
class Solution {
    /**
     *  1. 先对区间集合进行排序(根据开始位置)
     *  2. 合并的情况一共有三种
     *    a.                        b.                   c.
     *          |---------|             |--------|             |--------|
     *              |---------|              |--|                            |--------|
     *      a和b两种情况,合并取右边界大的值,c情况不合并
     *  
     */

    private int[][] tmp;
    
    public int[][] merge(int[][] intervals) {
        if(intervals == null ||intervals.length == 0)return new int[0][0];
        int length = intervals.length;
        //将列表中的区间按照左端点升序排序
        // Arrays.sort(intervals,(v1,v2) -> v1[0]-v2[0]);
        
        this.tmp = new int[length][2];
        sort(intervals,0,length-1);

        int[][] ans = new int[length][2];
        int index = -1;
        for(int[] interval:intervals){
            // 当结果数组是空是,或者当前区间的起始位置 > 结果数组中最后区间的终止位置(即上图情况c);
            // 则不合并,直接将当前区间加入结果数组。
            if(index == -1 || interval[0] > ans[index][1]){
                ans[++index] = interval;
            }else{
                // 反之将当前区间合并至结果数组的最后区间(即上图情况a,b)
                ans[index][1] = Math.max(ans[index][1],interval[1]);
            }
        }
        return Arrays.copyOf(ans, index + 1);
    }

    //归并排序
    public void sort(int[][] intervals,int l,int r){
        if(l >= r)return;

        int mid = l + (r-l)/2;
        sort(intervals,l,mid);
        sort(intervals,mid+1,r);

        //合并
        int i=l,j=mid+1;
        for(int k=l;k<=r;k++){
            if(i>mid)tmp[k]=intervals[j++];
            else if(j>r)tmp[k]=intervals[i++];
            else if(intervals[i][0]>intervals[j][0])tmp[k] = intervals[j++];
            else tmp[k] = intervals[i++];
        }

        System.arraycopy(tmp,l,intervals,l,r-l+1);
    }

}
B
BruceCat 已提交
158 159 160 161
```

### c++

K
Kian Kwok 已提交
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
[Kian](https://github.com/KianKw/) 提供第 56 题 C++ 代码

```c++
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        // len 为 intervals 的长度
        int len = intervals.size();
        if (len < 1)
            return {};

        // 按区间的 start 升序排列
        sort(intervals.begin(), intervals.end());

        // 初始化 res 数组
        vector<vector<int>> res;
        res.push_back(intervals[0]);

        for (int i = 1; i < len; i++) {
            vector<int> curr = intervals[i];
            // res.back() 为 res 中最后一个元素的索引
            if (curr[0] <= res.back()[1]) {
                // 找到最大的 end
                res.back()[1] = max(res.back()[1], curr[1]);
            } else {
                // 处理下一个待合并区间
                res.push_back(curr);
            }
        }
        return res;
    }
};
```
B
brucecat 已提交
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237



### javascript

[56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/)

```js
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function (intervals) {
    if (intervals.length < 1) {
        return []
    }

    // 按区间的 start 升序排列
    intervals.sort((a, b) => {
        return a[0] - b[0]
    })

    const res = []
    res.push(intervals[0].concat())

    for (let i = 1; i < intervals.length; i++) {

        let curr = intervals[i]
        // res 中最后一个元素的引用
        let last = res[res.length - 1]

        if (curr[0] <= last[1]) {
            // 找到最大的 end
            last[1] = Math.max(last[1], curr[1])
        } else {
            // 处理下一个待合并区间
            res.push(curr.concat())
        }
    }
    return res
}
```