Vvvrf / DSA.log← 返回全部笔记
LEETCODE 283Easy

移动零

把“搬运零”反过来想成“收拢非零元素”,一个慢指针就能标记它们该落的位置。

问题拆解

给定数组 nums,要把所有 0 移到末尾,同时保持非零元素原来的相对顺序,而且必须原地修改、不能另开数组。

如果盯着“零”看,很容易想到反复把某个零和后面的元素交换,但这样每个零都可能被搬运多次,操作次数很难控制。换个角度:末尾的零其实不需要我们主动去搬,只要把所有非零元素依次压到数组前部,剩下的位置自然全是零。

与其思考“零该去哪”,不如思考“下一个非零元素该落在哪个下标”。

慢指针记录落点

slow 指向下一个非零元素应该存放的位置,fast 负责扫描整个数组。每遇到一个非零元素,就把它交换到 slow 处,然后 slow 前移一格。

def moveZeroes(nums):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow], nums[fast] = nums[fast], nums[slow]
            slow += 1

slow 左边(不含 slow)始终是已经排好的非零元素,slowfast 之间则是一段零。当 fast 撞见非零元素时,这次交换恰好用它换回前面那个零,非零元素顺序不会被打乱。

为什么不用担心把已放好的元素换坏?因为当 slow == fast 时交换的是同一个位置,等于什么都没做;只有当中间积压了零,slow 才会落后于 fast,此时换过来的一定是零。

题目要求原地操作,所以函数直接修改 nums、返回 None,不要返回新数组。

复杂度

指标 复杂度 原因
时间 O(n) 数组只扫描一遍
空间 O(1) 仅用两个下标,原地交换

可以迁移的模式

  • 需要“原地”把满足条件的元素聚拢到一端;
  • 保持相对顺序,不能排序也不能借助额外数组;
  • 用一个慢指针标记“下一个合格元素的落点”,快指针负责发现。

移除元素、按奇偶重排、去掉重复项,都是这个“快慢指针分区”框架的变体。