1. 区间问题
思路
主要有两个技巧:
-
排序:常见的排序方法就是按照区间起点排序,或者先按照起点升序排序,若起点相同,则按照终点降序排序。当然,如果你非要按照终点排序,无非对称操作,本质都是一样的。
-
画图:就是说不要偷懒,勤动手,两个区间的相对位置到底有几种可能,不同的相对位置我们的代码应该怎么去处理。
leetcode题目
- 56 合并区间
- 986 区间列表的交集
- 1288 删除被覆盖区间
- 435 ⽆重叠区间
- 452 ⽤最少数量的箭引爆⽓球
- 1024 视频拼接
2. 题目
【 56 合并区间】
def merge(intervals):
'''
以数组 intervals 表示若⼲个区间的集合,其中单个区间为 intervals[i] = [starti, endi],请你将所有重叠的区间合并后返回。
leetcode:56. 合并区间
input: intervals = [[1,3],[2,6],[8,10],[15,18]]
output: [[1,6],[8,10],[15,18]]
思路:
1.
2.
3.
'''
if not intervals:
return []
intervals = sorted(intervals, key=lambda x: x[0])
res = [intervals[0]]
for i in range(1, len(intervals)):
cur = intervals[i]
# res 中最后一个元素的引用
last = res[-1]
if cur[0] <= last[1]:
last[1] = max(last[1], cur[1])
else:
res.append(cur)
print(res)
return res
【 986 区间列表的交集】
def intervalIntersection(firstList, secondList):
'''
给定两个由⼀些 闭区间 组成的列表,firstList 和 secondList,其中 firstList[i] = [starti, endi] ⽽ secondList[j] = [startj, endj]。
每个区间列表都是成对 不相交 的,并且 已经排序。 返回这 两个区间列表的交集。
形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,⽽ a <= x <= b。
两个闭区间的 交集是⼀组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3]。
leetcode: 986. 区间列表的交集
input::firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
思路:
1.
2.
3.
'''
i, j = 0, 0
res = []
while i < len(firstList) and j < len(secondList):
a1, a2 = firstList[i][0], firstList[i][1]
b1, b2 = secondList[j][0], secondList[j][1]
# 两个list判定有交集的前提,
if b2 >= a1 and a2 >= b1:
res.append([max(a1, b1), min(a2, b2)])
# 两个list前进的步伐
if b2 < a2:
j += 1
else:
i += 1
return res
【1288 删除被覆盖区间】
def removeCoveredIntervals(intervals):
'''
给你⼀个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数⽬。
leetcode: 1288. 删除被覆盖区间
input: intervals = [[1,4],[3,6],[2,8]]
output: 2
思路:
1. 先将列表的起点按升序排序,然后将终点按照降序排序
2. 根据三种情况
1) 区间完全包含:此时需要删除的区间数+1
2) 区间半包含:此时需要更新right的值为右侧更大的值
3) 区间未包含:此时需要更新left和right值
3. 最后剩余数目就是原数减去删除的数目
'''
# 起点按照升序排序,终点按照降序排序
intervals = sorted(intervals, key=lambda x: (x[0], -x[1]))
left, right = intervals[0][0], intervals[0][1]
res = 0
for i in range(1, len(intervals)):
v = intervals[i]
if left <= v[0] and right >= v[1]:
res += 1 # 需要删除的区间+1
if v[0] <= right <= v[1]:
right = v[1]
if right < v[0]:
left = v[0]
right = v[1]
return len(intervals) - res
【435 ⽆重叠区间】
def eraseOverlapIntervals(intervals):
'''
给定⼀个区间的集合,计算需要移除区间的最⼩数量,使剩余区间互不重叠。
leetcode: 435. ⽆重叠区间
input:
output:
思路:
1.
2.
3.
'''
if len(intervals) == 0:
return 0
intervals = sorted(intervals, key=lambda x: x[1])
count = 1
end = intervals[0][1]
for i in range(1, len(intervals)):
start = intervals[i][0]
if start >= end:
count += 1
end = intervals[i][1]
return len(intervals) - count
【452 ⽤最少数量的箭引爆⽓球】
def findMinArrowShots(intervals):
'''
在⼆维空间中有许多圆形的⽓球,⼀个⽓球在 x 轴上的投影为⼀个坐标区间 [start, end]。
⼀⽀⼸箭可以沿着 x 轴从不同点垂直向上射出,如果在坐标 x 处射出⼀⽀箭,所有 start <= x <= end的⽓球都会被射爆。
给你⼀个数组 points,其中 points[i] = [start_i, end_i] 表示第 i 个⽓球的位置,可以射出的⼸箭的数量没有限制,计算引爆所有⽓球所必须射出的最⼩⼸箭数
leetcode: 452. ⽤最少数量的箭引爆⽓球
input: [[10,16],[2,8],[1,6],[7,12]]
output:2
思路:
1.
2.
3.
'''
intervals = sorted(intervals, key=lambda x: x[1])
count, end = 1, intervals[0][1] # count初始化的时候,设置为1,至少需要一根箭
for i in range(1, len(intervals)):
start = intervals[i][0]
if start >= end: # 说明需要一支箭
count += 1
end = intervals[i][1]
return count
【1024 视频拼接】
def videoStitching(clips, T):
'''
你将会获得⼀系列视频⽚段,这些⽚段来⾃于⼀项持续时⻓为 T 秒的体育赛事。 这些⽚段可能有所重叠,也可能⻓度不⼀。
视频⽚段 clips[i] 都⽤区间进⾏表示:开始于 clips[i][0] 并于 clips[i][1] 结束。
我们甚⾄可以对这些⽚段⾃由地再剪辑,例如⽚段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。
我们需要将这些⽚段进⾏再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的⽚段([0, T])。
返回所需⽚段的最⼩数⽬,如果⽆法完成该任务,则返回 -1。
leetcode:1024. 视频拼接
input:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10
output:3
思路:
1.首先对起点先升序排序,然后对终点降序排序
2.由于cur_end初始值是0,所以第二个whilw在第一次遍历的时候,会自动跳出,从而res++
3.
'''
if T == 0:
return 0
# 首先对起点先升序排序,然后对终点降序排序
clips = sorted(clips, key=lambda x: (x[0], -x[1]))
res, cur_end, next_end = 0, 0, 0
i = 0
# 由于cur_end初始值是0,所以第二个whilw在第一次遍历的时候,会自动跳出,从而res++
while i < len(clips) and clips[i][0] <= cur_end:
while i < len(clips) and clips[i][0] <= cur_end:
# 迭代更新next_end和i
# cur_end是上一轮迭代的,作用于第二个while
next_end = max(next_end, clips[i][1])
i += 1
res += 1
# 此时cur_end已变化,作用于第一个while
cur_end = next_end
if cur_end >= T:
return res
return -1
【测试例】
if __name__ == "__main__":
merge([[1, 3], [2, 6], [8, 10], [15, 18]])
print(eraseOverlapIntervals([[1, 2], [2, 3], [3, 4], [1, 3]]))
print(videoStitching([[0, 2], [4, 6], [8, 10], [1, 9], [1, 5], [5, 9]], 10))