题目描述
以数组 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] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104intervals[i].length == 20 <= starti <= endi <= 104
解题思路
将重叠的区间合并成一个区间,最终返回一个不重叠的区间数组。解题思路如下:
- 首先按照区间的起始位置对所有区间进行排序
- 初始化一个结果数组,将第一个区间加入结果数组
- 遍历剩余的区间,判断当前区间是否与结果数组中的最后一个区间重叠:
- 如果重叠则合并两个区间(更新解结果数组中最后一个区间的结束位置)
- 如果不重叠,则将当前区间添加到结果数组中
代码实现
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
ans := make([][]int, 0)
ans = append(ans, intervals[0])
for _, inteval := range intervals {
if interval[0] > ans[len(ans)-1][1] {
ans = append(ans, interval)
} else {
ans[len(ans)-1][1] = max(ans[len(ans)-1][1], interval[1])
}
}
return ans
}
- 将问题线性化(通过 sort.Slice 自定义排序规则实现)
- 将第一个区间加入到结果数组中作为第一个参考区间
- 处理其他所有区间,对于每个新的区间,判断该区间的起始位置是否比最新的参考区间的结束位置更大
- 如果是,则说明这是一个新的区间,加入到结果数组中作为新的参考区间
- 否则,合并新的区间和最后一个参考区间
时空复杂度分析
只需要遍历一次切片,因此 for 循环部分时间复杂度为 O(n)。
但是需要对整个切片进行排序,最优时间复杂度为 O(nlogn)。
综上,时间复杂度为 O(nlogn)。
最坏情况下,所有区间都是单独的区间,此时空间复杂度为 O(n)。
自己 solve 1
type interval struct {
start int
end int
}
type mySlice []interval
func (ms mySlice) Len() int {
return len(ms)
}
func (ms mySlice) Swap(i, j int) {
ms[i], ms[j] = ms[j], ms[i]
}
func (ms mySlice) Less(i, j int) bool {
return ms[i].start < ms[j].start
}
func merge(intervals [][]int) [][]int {
var mi mySlice
for _, intervalSlice := range intervals {
mi = append(mi, interval{
start: intervalSlice[0],
end: intervalSlice[1],
})
}
sort.Sort(mi)
ans := make([][]int, 0)
start, end := mi[0].start, mi[0].end
for i := 1; i < mi.Len(); i++ {
if mi[i].start > end {
ans = append(ans, []int{start, end})
start, end = mi[i].start, mi[i].end
} else {
end = max(end, mi[i].end)
}
}
ans = append(ans, []int{start, end})
return ans
}
自己 solve 2
type mySlice []int
func merge(intervals [][]int) [][]int {
var ms []mySlice
for _, interval := range intervals {
ms = append(ms, mySlice(interval))
}
sort.Slice(ms, func(i, j int) bool {
return ms[i][0] < ms[j][0]
})
ans := make([][]int, 0)
start, end := ms[0][0], ms[0][1]
for i := 1; i < len(ms); i++ {
if ms[i][0] > end {
ans = append(ans, []int{start, end})
start, end = ms[i][0], ms[i][1]
} else {
end = max(end, ms[i][1])
}
}
ans = append(ans, []int{start, end})
return ans
}
关键数组变化表
| 步骤 | 操作 | intervals | result | 说明 |
|---|---|---|---|---|
| 1 | 输入 | [[1,3],[2,6],[8,10],[15,18]] | - | 初始输入 |
| 2 | 排序 | [[1,3],[2,6],[8,10],[15,18]] | - | 已按起始位置排序,无需变化 |
| 3 | 初始化 | - | [[1,3]] | 将第一个区间加入结果数组 |
| 4 | 处理区间[2,6] | - | [[1,6]] | 与[1,3]重叠,合并为[1,6] |
| 5 | 处理区间[8,10] | - | [[1,6],[8,10]] | 与[1,6]不重叠,直接添加 |
| 6 | 处理区间[15,18] | - | [[1,6],[8,10],[15,18]] | 与[8,10]不重叠,直接添加 |
| 7 | 返回结果 | - | [[1,6],[8,10],[15,18]] | 最终结果 |
考察知识点
这道题主要考察以下知识点:
-
区间操作:理解和处理区间的重叠关系,判断两个区间是否重叠的条件。
-
贪心算法:通过先排序,然后线性处理的方式来解决问题,体现了贪心策略。
-
排序应用:按照区间起始位置排序是解决此类问题的关键前置步骤。
-
数组操作:对结果数组进行动态添加和更新操作。
-
边界条件处理:考虑边界情况,如区间端点的相等情况(如示例2中的[1,4]和[4,5])。
-
时间复杂度分析:
- 排序: O(n log n)
- 遍历合并: O(n)
- 整体: O(n log n)
-
空间复杂度分析:需要 O(n) 的空间来存储结果。
-
思维能力:需要思考如何通过排序将问题简化,使得可以用线性的方式解决。
这道题是区间问题的典型代表,掌握这类问题的解决方案对于解决其他区间相关问题(如区间调度、会议室安排等)都有很大帮助。核心思想是通过排序将问题线性化,然后进行扫描处理。