题目描述
思路解析动画文字版
这题的限制条件是解法选择的核心。
这和链表环 II 是同一个模型,只是 next 指针藏在数组值里。
建模:下标 i 指向 nums[i]:从下标 0 出发:0 指向 1,1 指向 3,3 指向 2,2 指向 4,4 又指回 2。重复数字 2 不是凭空出现的,它是这个环的入口。
初始化:slow = fast = nums[0] = 1:代码从 nums[0] 开始,而不是从下标 0 开始;之后每一步都按 nums[当前节点] 往前走。
第一轮·fast 第一跳:fast 先到 3:fast 一轮要走两步,先看它的第一跳:从 1 沿 nums[1] 走到 3。
第一轮·收尾:slow 到 3,fast 第二跳到 2:slow 这轮只走一步停在 3;fast 接着第二跳从 3 走到 2。一轮过后 slow=3、fast=2。
第二轮:slow 和 fast 在 2 相遇:第一次相遇证明路径进入了环,但这个相遇点只是环内某点,还要继续找环入口。
重置 slow 到起点 nums[0]:Floyd 第二阶段:slow 回到起点,fast 留在相遇点,两个指针之后都每次走一步。
第二阶段第一步:slow 到 3,fast 到 4:两条路径到环入口的距离会同步抵消,所以只需要同速前进。
第二阶段第二步:slow 和 fast 在入口 2 相遇:第二次相遇的位置就是环入口。这里入口节点是 2,所以重复数字是 2。
返回 slow:重复数是 2:下标 3 和下标 4 这两个不同位置都填了 2,于是两条边一起指向节点 2,这就是“重复”在函数图里长出环的样子。
这题真正要学的是“数组函数图”建模:看到值域恰好能做下标,就该想到连边找环。
边界三连:寻找重复数 的边界先看最小规模、特殊输入和最容易触发分支的样例。
面试追问:寻找重复数 的追问重点,是把快慢指针两阶段的“为什么”讲成自己的话,而不只是背两个 while。
参考代码
class Solution: def findDuplicate(self, nums): slow = nums[0] fast = nums[0] while True: slow = nums[slow] fast = nums[nums[fast]] if slow == fast: break slow = nums[0] while slow != fast: slow = nums[slow] fast = nums[fast] return slow复杂度
- 时间复杂度:O(n),第一阶段相遇、第二阶段找入口都在线性步数内完成,合起来仍是 O(n)。
- 空间复杂度:O(1),全程只新增 slow、fast 两个整数变量,不开数组也不开集合。
可套用的代码模板
背这三步就行:定义 next、阶段1先走后判找相遇、阶段2回起点同速找入口。换成链表环 II 时 next 就是 node.next,骨架一字不改。
# 通用骨架:把题转成 next(x) 后照填三步def floyd_entry(start, nxt): # 阶段1:先走后判,找环内相遇点 slow = fast = start while True: slow = nxt(slow) # 慢: 1 步 fast = nxt(nxt(fast)) # 快: 2 步 if slow == fast: break # 阶段2:slow 回起点,两者同速找入口 slow = start while slow != fast: slow = nxt(slow); fast = nxt(fast) return slow # 入口# 287 套用:next(x)=nums[x], start=nums[0]易错点
面试追问把动画讲成自己的话
追问为什么第一阶段 fast 要走两步、slow 走一步?
追问第二阶段为什么“拨回起点再同速走”就能停在入口?
追问如果题目不保证恰好一个重复数,这个解法还成立吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
LRU 缓存
LeetCode 146 · 中等 · 沿着 链表 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题