题目描述
思路解析
一句话答案:LeetCode 646 最长数对链:把数对按右端点从小到大排序,再一遍扫描,左端点严格大于当前链尾 end 就接上、否则跳过。链尾右端点尽量小才给后面留空间,贪心即最优,时间 O(n log n)、空间 O(1)。
最长数对链这道题到底在接什么
给一个数对数组 pairs,每个数对是 [left, right] 且 left 小于 right。规则是:只要前一个数对的 right 严格小于后一个的 left,前一个就能接到后一个前面。要挑出尽量多的数对接成一条链,返回这条链最长能有多长。数对可以跳着挑、不必按原顺序,比如 pairs=[[1,2],[7,8],[4,5]],[1,2] 接 [4,5] 再接 [7,8],链长 3。
为什么把所有接法都列一遍会爆
最直接的想法是把所有能接成的链都摆出来,挑最长的。可每个数对选或不选、还要考虑接的先后,组合随数对个数指数级膨胀,数对一多就列不完。不去枚举整条链,改成从左到右扫一遍,边扫边用贪心(每一步只做当下最划算的选择、定了不反悔)决定每个数对接不接。
为什么按右端点排序而不是左端点
先把所有数对按右端点从小到大排好,再从左往右扫。为什么盯右端点?一条链还能接多长,卡在链尾的右端点上——链尾右端点越小,后面数对的左端点越容易越过它,留给后面的空间就越大。所以每次都优先接右端点小的数对,让链尾停得尽量靠前。若改按左端点排,可能先接了一个左端点小、右端点却很大的数对,把后面一大片空间堵死,反而漏掉更优的接法。
扫描时凭一个 end 门槛怎么决定接不接
排好序后用一个变量 end 记住当前链尾的右端点,初值设成一个极小的数,链长 ans 从 0 起。挨个看每个数对:它的左端点只要严格大于 end,就说明能接到链尾后面,把 ans 加一、并把 end 换成这个数对的右端点,门槛随之抬高;左端点没超过 end 就跳过它。这里必须是严格大于,题目要求前一个 right 严格小于后一个 left,两端相等算重叠、接不上。跳过的数对也不亏:它的右端点不比当前 end 小,留着它只会让链尾更靠后、更挤,丢掉不影响后面。
拿题面的三个数对亲手接一遍
拿题面的 pairs=[[1,2],[7,8],[4,5]] 走一遍。先按右端点排序,三个右端点分别是 2、8、5,排完是 [1,2]、[4,5]、[7,8]。end 起手是极小值、ans=0。第一个 [1,2],左端点 1 远大于 end,接上,ans 变 1,end 更新成 2。第二个 [4,5],左端点 4 严格大于 2,接上,ans 变 2,end 更新成 5。第三个 [7,8],左端点 7 严格大于 5,接上,ans 变 3,end 更新成 8。三个都接进来了,答案 3,和题面输出对上。
复杂度是多少,end 初值和相等两个坑
开销集中在排序,O(n log n)(大 O 记号描述规模变大时操作数怎么涨),n 是数对个数;排完只扫一遍是 O(n),加起来仍由排序主导。只用了 end、ans 两个变量,空间 O(1)。三个边界最容易踩坑:一是 end 初值要设成足够小的负数,数对可能带负数,设成 0 会让第一个数对接不进来;二是判断写成左端点大于等于 end 就错了,相等的两端接上会重叠,必须严格大于;三是只有一个数对时直接返回 1,它自己就算一条链。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这套「按右端点排序,左端点能越过 end 就接、否则跳」,下面每一帧都在套它。
先看原始 8 个数对,右端点是乱的:[1,2]、[7,8]、[4,5]、[2,3]、[5,6]、[3,4]、[6,7]、[1,9]。贪心的前提是把它们按右端点排好。
按右端点从小到大排好:[1,2]、[2,3]、[3,4]、[4,5]、[5,6]、[6,7]、[7,8]、[1,9]。这条轴上每个数字就是对应数对的右端点,左端点写在讲解里。现在从左往右贪心扫。
开局:还没接任何数对,把 end 设成极小(这样第一个数对一定能接进来),链长 ans=0。从最左边那个数对开始看。
轮到数对 [1,2](紫色,右端点 2)。它能不能接到链尾,只看它的左端点 1 是否严格大于当前 end = 负无穷。绿色是已接入的,灰色是已跳过的。
1 严格大于 end,能接!把 [1,2] 接进链(变绿),链长涨到 1,并把 end 更新成它的右端点 2(下一个数对要越过的就是这个新门槛)。
轮到数对 [2,3](紫色,右端点 3)。它能不能接到链尾,只看它的左端点 2 是否严格大于当前 end = 2。绿色是已接入的,灰色是已跳过的。
2 没有严格大于 end = 2,接上去会和链尾重叠(红色),跳过 [2,3]。注意:它的右端点不比当前 end 小,留着它只会更挤,丢掉不亏。end 保持 2。
轮到数对 [3,4](紫色,右端点 4)。它能不能接到链尾,只看它的左端点 3 是否严格大于当前 end = 2。绿色是已接入的,灰色是已跳过的。
3 严格大于 end,能接!把 [3,4] 接进链(变绿),链长涨到 2,并把 end 更新成它的右端点 4(下一个数对要越过的就是这个新门槛)。
轮到数对 [4,5](紫色,右端点 5)。它能不能接到链尾,只看它的左端点 4 是否严格大于当前 end = 4。绿色是已接入的,灰色是已跳过的。
4 没有严格大于 end = 4,接上去会和链尾重叠(红色),跳过 [4,5]。注意:它的右端点不比当前 end 小,留着它只会更挤,丢掉不亏。end 保持 4。
轮到数对 [5,6](紫色,右端点 6)。它能不能接到链尾,只看它的左端点 5 是否严格大于当前 end = 4。绿色是已接入的,灰色是已跳过的。
5 严格大于 end,能接!把 [5,6] 接进链(变绿),链长涨到 3,并把 end 更新成它的右端点 6(下一个数对要越过的就是这个新门槛)。
轮到数对 [6,7](紫色,右端点 7)。它能不能接到链尾,只看它的左端点 6 是否严格大于当前 end = 6。绿色是已接入的,灰色是已跳过的。
6 没有严格大于 end = 6,接上去会和链尾重叠(红色),跳过 [6,7]。注意:它的右端点不比当前 end 小,留着它只会更挤,丢掉不亏。end 保持 6。
轮到数对 [7,8](紫色,右端点 8)。它能不能接到链尾,只看它的左端点 7 是否严格大于当前 end = 6。绿色是已接入的,灰色是已跳过的。
7 严格大于 end,能接!把 [7,8] 接进链(变绿),链长涨到 4,并把 end 更新成它的右端点 8(下一个数对要越过的就是这个新门槛)。
轮到数对 [1,9](紫色,右端点 9)。它能不能接到链尾,只看它的左端点 1 是否严格大于当前 end = 8。绿色是已接入的,灰色是已跳过的。
1 没有严格大于 end = 8,接上去会和链尾重叠(红色),跳过 [1,9]。注意:它的右端点不比当前 end 小,留着它只会更挤,丢掉不亏。end 保持 8。
扫完全部 8 个数对,绿色这 4 个就是接成的最长链:[1,2] → [3,4] → [5,6] → [7,8]。灰掉的都是接不进来的。答案 4。
边界要点:单数对返回 1、相邻边界相等接不上、负数同样适用。
两个高频追问:与 DP 的取舍、与区间贪心家族的关系。
参考代码
from typing import Listclass Solution: def findLongestChain(self, pairs: List[List[int]]) -> int: pairs.sort(key=lambda x: x[1]) ans = 0 end = -10**18 for a, b in pairs: if a > end: ans += 1 end = b return ans复杂度
- 时间:O(n log n),排序占主导,之后扫描只 O(n)
- 空间:O(1),只用 ans、end 两个变量(排序原地)
易错点
面试追问把动画讲成自己的话
追问这题也能用动态规划,和贪心比怎么选?
追问它和「无重叠区间 LC435」是同一类吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
只有两个键的键盘
LeetCode 650 · 中等 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题