难度中等
题目描述
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
示例 3:
输入:intervals = [[4,7],[1,4]]
输出:[[1,7]]
解释:区间 [1,4] 和 [4,7] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104 intervals[i].length == 2 0 <= starti <= endi <= 104
解法
方法一:排序 + 一次遍历
思考
重叠关系在无序时难以定位。若反复任取区间与其余区间两两合并,最坏时间 \(O(n^2)\),且合并顺序杂乱。\(n \le 10^4\) 偏紧。
按左端点排序后,一个区间只会与「当前尚未闭合的那段」重叠:后续区间的起点更大,不可能跳过中间再叠回来。
因此排序后扫一遍,用一对端点维护正在合并的区间,一旦与下一个断开就写入答案。
我们可以将区间按照左端点升序排列,然后遍历区间进行合并操作。
具体的合并操作如下。
我们先将第一个区间加入答案,然后依次考虑之后的每个区间:
- 如果答案数组中最后一个区间的右端点小于当前考虑区间的左端点,说明两个区间不会重合,因此我们可以直接将当前区间加入答案数组末尾;
- 否则,说明两个区间重合,我们需要用当前区间的右端点更新答案数组中最后一个区间的右端点,将其置为二者的较大值。
最后,我们返回答案数组即可。
时间复杂度 \(O(n \times \log n)\),空间复杂度 \(O(\log n)\)。其中 \(n\) 为区间个数。
1
2
3
4
5
6
7
8
9
10
11
12
13 | class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
ans = []
st, ed = intervals[0]
for s, e in intervals[1:]:
if ed < s:
ans.append([st, ed])
st, ed = s, e
else:
ed = max(ed, e)
ans.append([st, ed])
return ans
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
int st = intervals[0][0], ed = intervals[0][1];
List<int[]> ans = new ArrayList<>();
for (int i = 1; i < intervals.length; ++i) {
int s = intervals[i][0], e = intervals[i][1];
if (ed < s) {
ans.add(new int[] {st, ed});
st = s;
ed = e;
} else {
ed = Math.max(ed, e);
}
}
ans.add(new int[] {st, ed});
return ans.toArray(new int[ans.size()][]);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
int st = intervals[0][0], ed = intervals[0][1];
vector<vector<int>> ans;
for (int i = 1; i < intervals.size(); ++i) {
if (ed < intervals[i][0]) {
ans.push_back({st, ed});
st = intervals[i][0];
ed = intervals[i][1];
} else {
ed = max(ed, intervals[i][1]);
}
}
ans.push_back({st, ed});
return ans;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | func merge(intervals [][]int) (ans [][]int) {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
st, ed := intervals[0][0], intervals[0][1]
for _, e := range intervals[1:] {
if ed < e[0] {
ans = append(ans, []int{st, ed})
st, ed = e[0], e[1]
} else if ed < e[1] {
ed = e[1]
}
}
ans = append(ans, []int{st, ed})
return ans
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | function merge(intervals: number[][]): number[][] {
intervals.sort((a, b) => a[0] - b[0]);
const ans: number[][] = [];
let [st, ed] = intervals[0];
for (const [s, e] of intervals.slice(1)) {
if (ed < s) {
ans.push([st, ed]);
[st, ed] = [s, e];
} else {
ed = Math.max(ed, e);
}
}
ans.push([st, ed]);
return ans;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | impl Solution {
pub fn merge(mut intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
intervals.sort_unstable_by(|a, b| a[0].cmp(&b[0]));
let n = intervals.len();
let mut res = vec![];
let mut i = 0;
while i < n {
let l = intervals[i][0];
let mut r = intervals[i][1];
i += 1;
while i < n && r >= intervals[i][0] {
r = r.max(intervals[i][1]);
i += 1;
}
res.push(vec![l, r]);
}
res
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | public class Solution {
public int[][] Merge(int[][] intervals) {
intervals = intervals.OrderBy(a => a[0]).ToArray();
int st = intervals[0][0], ed = intervals[0][1];
var ans = new List<int[]>();
for (int i = 1; i < intervals.Length; ++i) {
if (ed < intervals[i][0]) {
ans.Add(new int[] { st, ed });
st = intervals[i][0];
ed = intervals[i][1];
} else {
ed = Math.Max(ed, intervals[i][1]);
}
}
ans.Add(new int[] { st, ed });
return ans.ToArray();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | class Solution {
fun merge(intervals: Array<IntArray>): Array<IntArray> {
intervals.sortBy { it[0] }
val result = mutableListOf<IntArray>()
val n = intervals.size
var i = 0
while (i < n) {
val left = intervals[i][0]
var right = intervals[i][1]
while (true) {
i++
if (i < n && right >= intervals[i][0]) {
right = maxOf(right, intervals[i][1])
} else {
result.add(intArrayOf(left, right))
break
}
}
}
return result.toTypedArray()
}
}
|
方法二
思考
方法一已按左端排序并一次扫描完成合并,但额外维护了一对端点,区间闭合时才写入,结束时还要再补一次。
可以将「当前区间」直接放在答案末尾:先将第一个区间放入 \(\textit{ans}\),之后只更新末尾右端点或追加新区间,从而少一组变量、少一次收尾。
| class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
ans = [intervals[0]]
for s, e in intervals[1:]:
if ans[-1][1] < s:
ans.append([s, e])
else:
ans[-1][1] = max(ans[-1][1], e)
return ans
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> ans = new ArrayList<>();
ans.add(intervals[0]);
for (int i = 1; i < intervals.length; ++i) {
int s = intervals[i][0], e = intervals[i][1];
if (ans.get(ans.size() - 1)[1] < s) {
ans.add(intervals[i]);
} else {
ans.get(ans.size() - 1)[1] = Math.max(ans.get(ans.size() - 1)[1], e);
}
}
return ans.toArray(new int[ans.size()][]);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
vector<vector<int>> ans;
ans.emplace_back(intervals[0]);
for (int i = 1; i < intervals.size(); ++i) {
if (ans.back()[1] < intervals[i][0]) {
ans.emplace_back(intervals[i]);
} else {
ans.back()[1] = max(ans.back()[1], intervals[i][1]);
}
}
return ans;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12 | func merge(intervals [][]int) (ans [][]int) {
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] })
ans = append(ans, intervals[0])
for _, e := range intervals[1:] {
if ans[len(ans)-1][1] < e[0] {
ans = append(ans, e)
} else {
ans[len(ans)-1][1] = max(ans[len(ans)-1][1], e[1])
}
}
return
}
|
1
2
3
4
5
6
7
8
9
10
11
12 | function merge(intervals: number[][]): number[][] {
intervals.sort((a, b) => a[0] - b[0]);
const ans: number[][] = [intervals[0]];
for (let i = 1; i < intervals.length; ++i) {
if (ans.at(-1)[1] < intervals[i][0]) {
ans.push(intervals[i]);
} else {
ans.at(-1)[1] = Math.max(ans.at(-1)[1], intervals[i][1]);
}
}
return ans;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | public class Solution {
public int[][] Merge(int[][] intervals) {
intervals = intervals.OrderBy(a => a[0]).ToArray();
var ans = new List<int[]>();
ans.Add(intervals[0]);
for (int i = 1; i < intervals.Length; ++i) {
if (ans[ans.Count - 1][1] < intervals[i][0]) {
ans.Add(intervals[i]);
} else {
ans[ans.Count - 1][1] = Math.Max(ans[ans.Count - 1][1], intervals[i][1]);
}
}
return ans.ToArray();
}
}
|
方法三
思考
方法二已在答案末尾原地更新右端点,但仍是逐个区间判断、重叠则改写。
还可以按组合并:固定当前段的左端,用内层循环吞并所有与之相交的区间并拉大右端,再一次性写入,无需回头修改已写入的答案。