题目描述
思路解析动画文字版
结束早的区间不可能再和对方后面的区间产生交集,可以安全丢掉。
1. 读样例:两个有序区间列表求交集:上方数组是 A=[[0,2],[5,10],[13,23]],右侧面板是 B=[[1,5],[8,12],[15,24]]。两个列表各自有序且不重叠,目标是求出它们重叠的所有区间。
2. 初始化:i 指 A、j 指 B,都从 0 开始:i 指向 A 的第 0 个区间 [0,2](橙色高亮),j 指向 B 的第 0 个区间 [1,5]。两个指针都只前进不后退,答案列表 ans 暂时为空。
3. 第 1 轮取端点:A[0]=[0,2],B[0]=[1,5]:比较 A[0]=[0,2] 与 B[0]=[1,5]。交集左端取两者左端的较大值 max(0,1)=1,右端取两者右端的较小值 min(2,5)=2。
4. 第 1 轮判定:left 不超过 right,记录交集:left=1 不超过 right=2,说明两个区间真有重叠,把交集 [1,2] 加入 ans,A[0] 标绿表示已贡献一个交集。
5. 第 1 轮收尾:谁右端小谁先走(A 的 2 小):A 当前区间右端 2 小于 B 当前区间右端 5,A[0] 已经用完、不可能再和 B 后面的区间相交,所以 i 前进到 1,指向 A[1]=[5,10]。
6. 第 2 轮取端点:A[1]=[5,10],B[0]=[1,5]:现在比较 A[1]=[5,10] 与 B[0]=[1,5]。左端 max(5,1)=5,右端 min(10,5)=5,左右端相等,是一个单点候选。
7. 第 2 轮判定:端点相等也算交集:left=5 不超过 right=5(相等也成立),所以单点交集 [5,5] 也要记录。判定用「不超过」而非「小于」正是为了不漏掉这种端点相等的情况。
8. 第 2 轮收尾:B 的右端 5 更小,j 右移:A 当前右端 10 不小于 B 当前右端 5,这次该移动 B:j 前进到 1,B[j] 切换为 [8,12]。i 仍停在 A[1]=[5,10],同一个 A 区间可能和多个 B 区间相交。
9. 第 3 轮取端点:A[1]=[5,10],B[1]=[8,12]:同一个 A[1]=[5,10] 接着和新的 B[1]=[8,12] 比。左端 max(5,8)=8,右端 min(10,12)=10。
10. 第 3 轮判定:记录交集 [8,10]:left=8 不超过 right=10,记录交集 [8,10]。这是 A[1] 贡献的第二个交集,印证了一个 A 区间可以和多个 B 区间相交。
11. 第 3 轮收尾:A 的右端 10 更小,i 右移:A 当前右端 10 小于 B 当前右端 12,A[1] 用完,i 前进到 2,指向 A[2]=[13,23]。
12. 第 4 轮取端点:A[2]=[13,23],B[1]=[8,12]:比较 A[2]=[13,23] 与 B[1]=[8,12]。左端 max(13,8)=13,右端 min(23,12)=12,这一轮 left 比 right 大。
13. 第 4 轮判定:left 比 right 大,无交集:left=13 比 right=12 大,说明两个区间没有重叠,这一轮不记录任何交集。A[2] 暂时标灰表示本轮没贡献交集。
14. 第 4 轮收尾:B 的右端 12 更小,j 右移:A 当前右端 23 不小于 B 当前右端 12,移动 B:j 前进到 2,B[j] 切换为 [15,24]。即使没记录交集,指针也照常推进。
15. 第 5 轮取端点:A[2]=[13,23],B[2]=[15,24]:A[2]=[13,23] 接着和 B[2]=[15,24] 比。左端 max(13,15)=15,右端 min(23,24)=23。
16. 第 5 轮判定:记录交集 [15,23]:left=15 不超过 right=23,记录交集 [15,23],A[2] 标绿。至此已收集到 4 个交集。
17. 第 5 轮收尾:A 右端 23 更小,i 移出界:A 当前右端 23 小于 B 当前右端 24,i 前进到 3。A 一共只有 3 个区间,i=3 已经到头,下一次循环条件不再满足。
18. 循环结束:任一列表走完就停:i 已经等于 A 的长度,while 循环条件不再成立,扫描结束。任何一个列表走完,剩下的区间都不可能再产生交集,直接返回 4 个交集。
19. 不变量回顾:结束早的先走,指针只进不退:贯穿全程的规律:交集左端取两者左端较大值、右端取较小值;谁的右端更小谁就被耗尽、指针前进。两个指针都只前进不后退,总共 O(m+n) 次比较。
有序区间双指针的关键是“结束早的先走”。
参考代码
class Solution: def intervalIntersection(self, firstList, secondList): i = j = 0 ans = [] while i < len(firstList) and j < len(secondList): a1, a2 = firstList[i] b1, b2 = secondList[j] left = max(a1, b1) right = min(a2, b2) if left <= right: ans.append([left, right]) if a2 < b2: i += 1 else: j += 1 return ans复杂度
- 时间复杂度:O(m+n),每次移动一个指针
- 空间复杂度:O(1),不计返回结果
可套用的代码模板
这段代码可以按 LeetCode Python 模板直接提交;变量名和动画里的核心状态保持一致。
Python
class Solution: def scanIntervals(self, a, b): i = j = 0 ans = [] while i < len(a) and j < len(b): left = max(a[i][0], b[j][0]) right = min(a[i][1], b[j][1]) if left <= right: ans.append([left, right]) if a[i][1] < b[j][1]: i += 1 else: j += 1 return ans易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
戳气球
LeetCode 312 · 困难 · 沿着 区间套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题