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]
输入:nums = [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 后续单元固定一个数+双指针+去重