题目描述
思路解析动画文字版
最直觉的写法:每遇到一个 0,就把它后面的元素整体往前挪一格、再把 0 补到末尾。一个 0 就要挪一整段,最坏要挪 n 的平方次,太慢。
换个思路:让两个指针分工。fast 一路向右找非零;每找到一个,就和 slow 位置交换,再让 slow 前进一格。fast 是从左到右按原顺序遇到非零的,依次落到 slow,相对顺序天然不乱;slow 左边永远是已归位的非零,末尾自然就剩 0。
起点 · slow=0, fast=0:开局两个指针都在 0 号位。slow 记着「下一个非零应该落在哪」,fast 负责往右探路找非零。
fast=0 · 看到 0,跳过:fast 当前指着 0。这不是非零,什么都不做,slow 留在 0 号位继续守坑,只让 fast 往右走一格。
fast=1 · 找到非零 1:fast 走到 1,是非零!该把它和 slow 守的 0 号坑交换,让 1 归位到最前。注意被点亮的两格 0 号和 1 号,就是要互换的一对。
交换后 · 1 归位,slow→1:1 和 0 号位的 0 互换,数组变成 [1,0,0,3,12],1 归位到最前。因为放进了一个非零,slow 才前进到 1 号位,去守下一个坑。
fast=2 · 又一个 0,跳过:fast 又遇到 0,跳过,slow 仍停在 1 号位等下一个非零。看出节奏了吗,slow 只在「放数」那一刻才动。
fast=3 · 找到非零 3:fast 找到非零 3,准备和 slow 守的 1 号坑交换。被点亮的 1 号和 3 号就是这次互换的一对。
交换后 · 3 归位,slow→2:3 和 1 号位的 0 互换,数组变成 [1,3,0,0,12],3 归位。slow 前进到 2 号位。前面已经稳稳是 [1,3] 了。
fast=4 · 找到非零 12:fast 走到末尾找到非零 12,准备和 slow 守的 2 号坑交换。点亮的 2 号和 4 号就是最后这对。
交换后 · 完成!:12 和 2 号位的 0 互换,数组变成 [1,3,12,0,0]。slow 左边 [1,3,12] 全是按原序归位的非零,末尾自然剩两个 0。一趟扫完,搞定。
灵魂帧慢放 · slow 守坑、fast 探路:把这题最该记住的一刻定格放慢:slow 左边的 [1,3] 是已归位区,slow 此刻守着 2 号坑,fast 在 3 号位探路。fast 遇非零 → 和 slow 交换 → slow 才前进,三拍一循环。删掉所有文字,光看 slow 左边越来越长、fast 一路右探,也能看懂机制。
一句话本质:fast 是读指针、slow 是写指针,读到合格的就换到 slow 并前进。移除元素、删重复项、移动零,都是这套快慢指针读写分离。
雷区实演 · slow 每轮都 +1 会怎样:用一个会翻车的小例子 [0,1] 真跑错法:如果 fast=0 遇到 0 也让 slow++,slow 直接跳到 1 号位。等 fast=1 找到非零 1 时,slow 和 fast 都在 1 号位,自交换、原地不动,结果还是 [0,1],那个 0 永远没被移走。所以 slow 遇 0 绝不能动。
边界三连:三种极端都安全:全 0 不交换、全非零自交换、单元素不越界——交换法不需要任何特判。
面试追问:面试高频三连:交换 vs 覆盖的取舍、稳定性、能否少写——都从「slow 是写指针」这一句推得出来。
迁移阶梯:把这题练到能复述后,去同类题迁移:改一下「保留」条件就是 LC27 移除元素、LC26 删除有序数组重复项。想多刷可让 AI 助教小欧出几道快慢指针变体,或进通关训练连着练。
参考代码
class Solution: def moveZeroes(self, nums): slow = 0 for fast in range(len(nums)): if nums[fast] != 0: nums[slow], nums[fast] = nums[fast], nums[slow] slow += 1复杂度
- 时间复杂度:O(n),fast 把数组扫一遍,slow 只增不减;每个位置至多被碰一次
- 空间复杂度:O(1),原地交换,只用 slow / fast 两个下标,不开额外数组
可套用的代码模板
记住挖空骨架:fast 读、slow 写、合格才换并进位。换题只动「保留条件」那一行——移除元素改成不等于 val、删重复项改成不等于前一个。
# 「原地保留/移动符合条件的元素」都套这个骨架slow = 0 # 写指针:下一个合格元素该落的坑for fast in range(len(nums)): # 读指针:逐个扫 if 保留条件(nums[fast]): # ← 只改这一行 nums[slow], nums[fast] = nums[fast], nums[slow] slow += 1# 移动零: 保留条件 = nums[fast] != 0# 移除元素: 保留条件 = nums[fast] != val易错点
面试追问把动画讲成自己的话
追问为什么用交换而不是覆盖?两者各有什么取舍?
追问这个解法稳定吗(非零相对顺序会不会乱)?
追问能不能进一步减少写操作?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
颜色分类
LeetCode 75 · 中等 · 沿着 双指针 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题