8.重叠区间
重叠区间模式用于合并或处理数组中的重叠区间 。
在按开始时间排序的区间数组中,如果两个区间a,b和c,d重叠,则b>=c(即,第一个区间的结束时间大于或等于第二个区间的开始时间)。
核心:先排序,再遍历合并,通用解题步骤:
步骤 1:按区间起始点排序
先将所有区间按左端点(start)升序排序,这是处理重叠问题的前提。
排序后,我们只需要和已合并列表的最后一个区间比较,就能判断是否重叠。
步骤 2:初始化结果列表创建一个空列表 merged,用来存放最终合并后的区间。
若输入区间列表为空,直接返回空。
步骤 3:遍历并合并区间遍历排序后的每个区间 curr:
若 merged 为空,直接将 curr 加入 merged。
取 merged 中最后一个区间 last:
重叠判断 :curr.start <= last.end(因为已按 start 排序,只要当前区间的 start ≤ 上一个区间的 end,就一定重叠)。
重叠时合并 :更新 last.end 为 max(last.end, curr.end)(保留最大的结束值,覆盖重叠部分)。
不重叠时添加:将 curr 直接加入 merged。
| 场景 | 判断条件 | 操作 |
|---|---|---|
| 两个区间重叠 | curr.start ≤ last.end |
更新 last.end = max(last.end, curr.end) |
| 两个区间不重叠 | curr.start > last.end |
将 curr 追加到结果列表 |
合并区间(LeetCode #56)
题目描述
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervalsi = 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
intervalsi.length == 2
0 <= starti <= endi <= 104
题目求解
python
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x: x[0])
merged = []
for cur in intervals:
if not merged:
merged.append(cur)
else:
item = merged[-1]
if item[0] <= cur[1] and cur[0] <= item[1]:
merged.pop(-1)
merged.append([min(item[0], cur[0]), max(item[1], cur[1])])
else:
merged.append(cur)
return merged
稍微优化下代码,把if...else...语句缩一下,当merged为空 或者 cur区间 和 item 区间 不重合,直接append;否则,合并,并且区间开始已经排好序了,所以肯定是最小值,不需要min(item\[0, cur0)判断。代码如下:
python
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x:x[0])
merged = []
for num in intervals:
# 为空,或者 当前遍历的区间 和 merged最后一个区间 不重叠
if not merged or merged[-1][1] < num[0]:
merged.append(num)
else:
# 有重叠
merged[-1][1] = max(merged[-1][1], num[1])
return merged
插入区间(LeetCode#57)
题目描述
给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervalsi = starti, endi 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = start, end 表示另一个区间的开始和结束。
在 intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。
返回插入之后的 intervals。
注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。
示例 1:
输入:intervals = \[1,3,6,9], newInterval = 2,5
输出:\[1,5,6,9]
示例 2:
输入:intervals = \[1,2,3,5,6,7,8,10,12,16], newInterval = 4,8
输出:\[1,2,3,10,12,16]
解释:这是因为新的区间 4,8 与 3,5,6,7,8,10 重叠。
提示:
0 <= intervals.length <= 104
intervalsi.length == 2
0 <= starti <= endi <= 105
intervals 根据 starti 按 升序 排列
newInterval.length == 2
0 <= start <= end <= 105
题目求解
把新的区间 append 到 区间列表中,其实就转为第一个题了
python
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
merged = intervals
merged.append(newInterval)
merged.sort(key=lambda x: x[0])
ans = []
for num in merged:
if not ans or ans[-1][1] < num[0]:
ans.append(num)
else:
ans[-1][1] = max(ans[-1][1], num[1])
return ans
模拟------直接合并,但是需要考虑新的区间 的位置,思路:
- 阶段 1:遍历所有「右端点 < 新区间左端点」的区间 → 直接加入结果(这些区间和新区间完全不重叠,且在新区间左侧)。
- 阶段 2:遍历所有「左端点 ≤ 新区间右端点」的区间 → 合并这些区间和新区间(更新新区间的左右端点为合并后的极值)。
- 阶段 3:将合并后的新区间加入结果,再遍历剩余所有区间 → 直接加入结果(这些区间在新区间右侧,完全不重叠)。
python
# class Solution:
# def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
# merged = intervals
# merged.append(newInterval)
# merged.sort(key=lambda x: x[0])
# ans = []
# for num in merged:
# if not ans or ans[-1][1] < num[0]:
# ans.append(num)
# else:
# ans[-1][1] = max(ans[-1][1], num[1])
# return ans
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
i = 0
n = len(intervals)
ans = []
# 右端点 < 新区间左端点 ->直接加入结果
while i < n and intervals[i][1] < newInterval[0]:
ans.append(intervals[i])
i += 1
# 看是否需要合并
while i < n and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(intervals[i][0], newInterval[0])
newInterval[1] = max(intervals[i][1], newInterval[1])
i += 1
ans.append(newInterval)
# 添加剩余元素
while i < n:
ans.append(intervals[i])
i += 1
return ans
纯模拟,但很多小问题,不过还是很考虑细节的。
不重叠区间(LeetCode#435)
题目描述
给定一个区间的集合 intervals ,其中 intervalsi = starti, endi 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
注意 只在一点上接触的区间是 不重叠的。例如 1, 2 和 2, 3 是不重叠的。
示例 1:
输入: intervals = \[1,2,2,3,3,4,1,3]
输出: 1
解释: 移除 1,3 后,剩下的区间没有重叠。
示例 2:
输入: intervals = \[1,2, 1,2, 1,2 ]
输出: 2
解释: 你需要移除两个 1,2 来使剩下的区间没有重叠。
示例 3:
输入: intervals = \[1,2, 2,3 ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
提示:
1 <= intervals.length <= 105
intervalsi.length == 2
-5 * 104 <= starti < endi <= 5 * 104
题目求解
贪心策略:选结束早的,能给后面留出更多的空间。
生活类比(最易理解)
假设你是一个时间管理大师,要在一天内安排尽可能多的「不重叠会议」:
- 会议 A:9:00-9:30(右端点 9:30)
- 会议 B:9:15-10:00(右端点 10:00)
- 会议 C:9:30-10:00(右端点 10:00)
你会优先选哪个?→ 选 A(9:00-9:30)!因为 A 结束得最早,选完 A 后还能选 C(9:30-10:00),总共能安排 2 个会议;→ 但如果选 B(9:15-10:00),就只能安排 1 个会议。
结论:选结束早(右端点小)的区间,能给后面留出更多「空闲时间」,从而容纳更多不重叠的区间。
优先选右端点最小的区间,能得到最多的不重叠区间数。(反证法 证得)
python
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
intervals.sort(key=lambda x: x[1])
ans = 1 # 至少能保留的区间数
n = len(intervals)
last_end = intervals[0][1]
for i in range(1, n):
if intervals[i][0] >= last_end:
ans += 1
last_end = intervals[i][1]
return n - ans
Python 中的排序 相关练习
python
# list.sort()
# 基础排序
# 列表原地排序
nums = [3, 1, 4, 2, 7]
nums.sort()
print(nums)
# 降序
nums.sort(reverse=True)
print(nums)
# 返回新列表排序
nums_sorted = [6, 4, 2, 7, 1, 9]
sorted_nums = sorted(nums_sorted)
print(sorted_nums)
# 降序
sorted_nums_desc = sorted(nums_sorted, reverse=True)
print(sorted_nums_desc)
# 进阶排序
# 按元素某个属性
intervals = [[2, 3], [1, 4], [5, 6]]
intervals.sort(key=lambda x: x[0])
print(intervals)
intervals.sort(key=lambda x: x[1])
print(intervals)
# 排序 字典/对象
d = {"a": 3, "b": 1, "c": 2}
sorted_d = sorted(d.items(), key=lambda x: x[1])
print(sorted_d)
# 自定义 对象的属性排序
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
student = [Student('A', 88), Student('B', 71), Student('C', 98)]
# 按照分数升序
student.sort(key=lambda s: s.score)
print([s.name for s in student])
# 按照字符串长度排序
words = ['apple', 'banana', 'cat', 'dog']
words.sort(key=lambda w: len(w))
print(words)
# 多条件 排序
# 先按分数降序,分数相同按名字升序
students = [
Student('Alice', 90),
Student('Bob', 85),
Student('Charlie', 90),
Student('David', 85)
]
# key=(分数(负号实现降序),名字)
students.sort(key=lambda s: (-s.score, s.name))
print([(s.name, s.score) for s in students])
# 排序不可变对象
s = 'python'
sorted_s = sorted(s)
print(sorted_s) # ['h', 'n', 'o', 'p', 't', 'y']
# 排序元组
tup = (3, 1, 2, 7, 4, 5)
sorted_t = sorted(tup)
print(sorted_t) # 返回的是排序后的列表
# python中的 排序是稳定排序
students = [
Student('Alice', 90),
Student('Bob', 85),
Student('Charlie', 90),
]
students.sort(key=lambda s: s.score)
print([s.name for s in students])