题目描述
原地把数组里所有 0 移到末尾,非零元素保持原来顺序。
思路解析
每遇到一个 0 就把后面元素整体前移,最坏要移 n² 次。换个思路:让非零元素一个个「向前归位」,0 自然就留在后面了。
fast 一路向前找非零;每找到一个,就和 slow 位置交换,slow 前进一格。因为 fast 是从左到右按原顺序遇到非零的,依次写入 slow,相对顺序天然不变;slow 左边永远是「已归位的非零」,最后空出来的位置自然是 0。
slow=0, fast=0 · 遇 0:负例分支:fast 当前是 0,不处理,slow 留在原地,只让 fast 往前走。slow 在等下一个非零来填它这个空位。
fast=1 · 找到非零 1:fast 找到非零 1。该把它和 slow(0号位)交换,让 1 归位到最前。
fast=1 · 交换 → slow 进位:1 和 0 交换,数组变 [1,0,0,3,12],1 归位到最前。因为放入了一个数,slow 才前进到 1。
fast=2 · 又遇 0:又一次负例:fast 是 0,跳过,slow 仍停在 1 号位等下一个非零。看出节奏了吗——slow 只在「放数」时才动。
fast=3 · 找到 3:fast 找到非零 3,准备和 slow(1号位)交换。
fast=3 · 交换 → slow 进位:3 和 1 号位的 0 交换,数组变 [1,3,0,0,12],3 归位。slow 前进到 2。前面已经是 [1,3] 了。
fast=4 · 找到 12:fast 走到最后,找到非零 12,准备和 slow(2号位)交换。
fast=4 · 交换 → 完成:12 和 2 号位的 0 交换,数组变 [1,3,12,0,0]。slow 左边 [1,3,12] 全是按原序归位的非零,后面自然剩两个 0。
slow 是「写指针」、fast 是「读指针」——读到合格的就写到 slow 并前进。删除元素、去重都是这套快慢指针。
参考代码(Python)
1slow = 0
2for fast in range(len(nums)):
3 if nums[fast] != 0: # 找到非零
4 nums[slow], nums[fast] = nums[fast], nums[slow] # 交换保顺序
5 slow += 1 # 放入了才进位复杂度分析
- 时间复杂度:O(n) —— fast 扫一遍,slow 只增不减,指针只走一遍
- 空间复杂度:O(1) —— 原地交换,不开额外数组
套路模板
记住骨架:fast 读、slow 写、合格才写并进位。删除有序数组重复项、移除元素都是改一下「保留」条件。
1# 「原地保留/移动符合条件的元素」都套
2slow = 0 # 写指针
3for fast in range(n): # 读指针
4 if 保留(nums[fast]): # 合格就写入
5 nums[slow] = nums[fast]; slow += 1
6# slow 左边就是结果,长度 = slow易错点
- 错误写法:直接 nums[slow]=nums[fast] 覆盖、不管 0 → 正确写法:用交换 swap(或先压缩非零、最后补 0)(覆盖会把本该留到末尾的 0 直接抹掉,得到的数组里 0 的个数都对不上;交换才同时保住顺序和那些 0)
- 错误写法:slow 每轮都 +1 → 正确写法:只有写入(交换)时才 slow += 1(slow 只在放入合格元素后前进,遇 0 时不动,否则归位位置会错乱)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。