1717 words
9 minutes
Leetcode Hot 100 移动零

Leetcode Hot 100 第四篇,轮到这道名字朴素、要求却一点不含糊的题:移动零

它看着很像一道“把零丢后面去就完了”的热身题, 但题目专门补了一句:

必须在不复制数组的情况下原地对数组进行操作。

这句话的潜台词就是:

  • 不能新开一个同等大小的数组偷懒
  • 不能先筛非零、再补零直接重建
  • 得在原数组上动手,把零往后挪
  • 同时还得保证非零元素的相对顺序不变

所以这题最顺手的正解,就是:双指针 / 快慢指针

题目链接#

LeetCode 283. 移动零

题目描述#

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意,必须在不复制数组的情况下原地对数组进行操作。

移动零题目截图

解题思路:快慢指针原地交换#

这题的关键,是把“非零元素往前放”这件事想明白。

我们并不需要盯着每个 0 想怎么搬家, 更高效的思路是:

扫描整个数组,看到非零元素,就把它放到前面该去的位置。

这样一来:

  • 前面会逐渐变成排好队的非零元素区
  • 后面剩下的位置,自然会慢慢变成零区

两个指针分别干什么?#

  • right:负责从左到右扫描数组
  • left:指向“下一个应该放非零元素的位置”

也就是说:

  • right 像巡逻的
  • left 像安排座位的

一旦 right 找到一个非零数, 就把它交换到 left 的位置, 然后 left 往后走一步,准备迎接下一个非零数。

代码实现#

class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
left = 0
for right in range(len(nums)):
if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1

代码解析#

1. left 指向下一个非零元素该放的位置#

left = 0

一开始,数组最前面的位置就是第一个非零数应该去的地方。

所以:

  • left = 0 表示“下一个非零元素先往 0 号位安排”

2. right 负责遍历整个数组#

for right in range(len(nums)):

right 会从头扫到尾,挨个看每个元素。

3. 只处理非零元素#

if nums[right] != 0:

如果当前元素是 0,那就先不管它。

因为这题真正重要的,不是“怎么处理零”, 而是:

怎么把所有非零元素稳定地往前收紧。

4. 把非零元素交换到前面该去的位置#

nums[left], nums[right] = nums[right], nums[left]
left += 1

这里有两层意思:

第一层:把当前非零元素放到前面#

比如 left = 1right = 3, 说明:

  • 前面第 1 个位置还空着,等着放非零元素
  • 当前在第 3 个位置找到了一个非零元素

那就直接交换。

第二层:left 往后走#

因为当前位置已经成功安置了一个非零元素, 所以下一个非零元素就该去更后面的位置了。

示例推演#

来看最经典的样例:

nums = [0, 1, 0, 3, 12]

初始状态#

  • left = 0
  • right0 开始往后扫

right = 0#

nums[right] = 0

是零,跳过。

数组不变:

[0, 1, 0, 3, 12]

right = 1#

nums[right] = 1

不是零,交换 nums[left]nums[right]

nums[0], nums[1] = nums[1], nums[0]

数组变成:

[1, 0, 0, 3, 12]

然后:

left = 1

right = 2#

nums[right] = 0

还是零,跳过。

数组不变:

[1, 0, 0, 3, 12]

right = 3#

nums[right] = 3

不是零,交换:

nums[1], nums[3] = nums[3], nums[1]

数组变成:

[1, 3, 0, 0, 12]

然后:

left = 2

right = 4#

nums[right] = 12

不是零,继续交换:

nums[2], nums[4] = nums[4], nums[2]

数组变成:

[1, 3, 12, 0, 0]

最后结果就是:

[1, 3, 12, 0, 0]

为什么这种方法能保持相对顺序不变#

这一点非常重要。

题目不是只让你把 0 扔后面, 还要求:

非零元素之间原本的先后顺序不能乱。

而这个写法恰好满足。

因为 right 是从左到右扫描的, 所以我们遇到非零元素的顺序,就是它们原本出现的顺序。

每次都把当前遇到的非零元素, 依次放到 left 指向的位置上。

于是前面的非零区就保持了原有次序。

简单说就是:

  • 谁先被 right 遇到
  • 谁就先被安排进前面的非零区

秩序很稳,不搞插队。

复杂度分析#

时间复杂度#

整个数组只遍历一遍,所以时间复杂度是:

O(n)

空间复杂度#

只用了两个额外指针变量,没有新建数组,所以空间复杂度是:

O(1)

这也正好满足题目要求的“原地操作”。

你的写法和标准写法的关系#

你给的代码本质上也是双指针思路,方向没跑偏:

left = 0
n = len(nums)
for right in range(n):
tmp = nums[left]
nums[left] = nums[right]
nums[right] = tmp
if nums[left] != 0:
left += 1
return nums

它很多情况下也能得到正确结果, 因为你其实也是在不断把非零元素往前换。

但从题解表达上看,标准写法更推荐下面这种:

1. 先判断是不是非零,再交换#

if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1

这样逻辑更清楚:

  • 只有非零元素才参与前移
  • 零元素直接跳过

2. 不需要返回数组#

这题要求是:

modify nums in-place instead

也就是说函数直接修改原数组即可, 不需要再 return nums

当然,写了返回值在很多语言环境下也不一定报错, 但从题意和规范表达上,最好别多此一举。

这种写法的关键点#

1. 题目要你“移动零”,本质却是“收紧非零”#

别被题目名字带偏。

真正高效的思路,不是盯着零怎么搬, 而是让非零元素一个个去前面坐好。

2. left 永远指向非零区的下一个空位#

这就是整个算法的核心定位。

3. 原地操作不代表不能交换#

原地操作只是说:

  • 不额外开新数组
  • 直接在原数组上修改

交换恰好就是最自然的一种原地处理方式。

小结#

这题不难,但特别适合建立双指针的基本手感。

如果只记一句话,那就是:

快指针负责找非零,慢指针负责安排座位。

于是整个过程就很顺了:

  • 扫到零,先略过
  • 扫到非零,就往前放
  • 扫完一遍,零自然全被挤到后面

Hot 100 第四篇,继续推进。 别小看这种基础题,很多高楼大厦,最开始都是从把这几个零挪明白开始盖起来的。🦐

Leetcode Hot 100 移动零
https://ssonnyboy.github.io/yoroziya/posts/leetcode-hot-100-move-zeroes/
Author
biabuluo
Published at
2026-03-27
License
CC BY-NC-SA 4.0