Python算法训练序列
双指针与滑动窗口 · Unit 02
← Unit 01
283
LeetCode 283Move Zeroes · 移动零
fast 负责读完整个数组,
slow 负责维护正确前缀。
本题不仅要求把零移动到末尾,还要求保持所有非零元素的相对顺序,并且必须原地修改数组。双指针的作用不是“寻找两个数”,而是将读取位置与写入位置分离。
LeetCode #283 原题内容
中文转述 · 示例、约束与提交签名
题目要求
给定整数数组 nums,将所有 0 移动到数组末尾,同时保持所有非零元素的相对顺序。必须在原数组上完成操作,不能复制一个额外数组。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
# 原地修改 nums,不返回新数组
约束
1 ≤ len(nums) ≤ 10000-2³¹ ≤ nums[i] ≤ 2³¹ - 1- 进阶:尽量减少操作次数。
示例
输入:nums = [0,1,0,3,12]
操作后:[1,3,12,0,0]
操作后:[1,3,12,0,0]
输入:nums = [0]
操作后:[0]
操作后:[0]
三个同时满足的要求
- 所有非零元素位于前部;
- 非零元素相对顺序不变;
- 使用 O(1) 额外空间。
1. 理解稳定压缩
2. 体验重复搬移
3. 建立读写指针
4. 代码联动
5. 删除元素迁移
演示数组:nums = [0,1,0,3,12]等待操作
[0, slow):已整理非零前缀
[slow, fast):已扫描的零区
fast:当前读取位置
当前循环不变量
[0, slow) 保存已扫描部分中的全部非零元素,且相对顺序不变。
当前移动规则
fast 遇到零只继续扫描;遇到非零则写入 slow,并让 slow 右移。
slow0
fast0
nums[fast]0
已整理长度0
数组状态未完成
朴素搬移的隐藏成本
每发现一个零,就把后面的所有元素左移一格。连续或大量零会导致相同元素被反复搬动,最坏达到 O(n²)。
补全 LeetCode 提交代码卡住可查看参考答案
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow =
for fast in range(len(nums)):
if :
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
为什么交换不会破坏非零顺序?slow始终是最靠左的待填位置,而fast按原顺序遇见非零元素。每个非零元素依次被放到slow,因此前缀中的顺序与原数组一致。若slow==fast,代码只是自交换,不影响结果。
代码—读写指针—数组联动轨迹:[0,1,0,3,12]
1class Solution:
2 def moveZeroes(self, nums: List[int]) -> None:
3 slow = 0
4 for fast in range(len(nums)):
5 if nums[fast] != 0:
6 nums[slow], nums[fast] = nums[fast], nums[slow]
7 slow += 1
点击下一行,观察fast如何读取,slow如何扩展正确前缀。
完成代码联动后进入迁移练习
补全正确、查看答案或播放完整轨迹后解锁。
迁移题:LeetCode #27 移除元素
给定数组 nums 和值 val,原地移除所有等于 val 的元素,并返回剩余元素数量。无需关心数组尾部内容。请沿用“fast读取、slow写入”的模式。
本单元形成的算法Skill
当数组需要原地过滤或压缩时,用 fast 扫描全部输入,用 slow 维护有效输出前缀。进入每轮时,[0, slow) 已经是处理完成的结果,[slow, fast) 是已扫描但不应留在前缀中的位置。
LeetCode #283
同向读写指针
稳定压缩
原地修改
时间 O(n)
额外空间 O(1)
#125 已完成相向指针+字符过滤
#283 当前:稳定压缩同向读写指针
#167 下一单元有序数组单调排除
#15 后续单元固定一个数+双指针+去重