题目描述
思路解析
一句话答案:LeetCode 283 移动零的标准解是快慢指针一趟遍历:慢指针始终指向下一个非零元素该放的空位,快指针向右扫描,遇到非零就和慢指针位置交换、慢指针前进一格,零被自然甩到末尾。原地完成、非零元素相对顺序不变,时间 O(n)、空间 O(1)。
这道题真正在问什么
原地把数组里所有 0 挪到末尾,同时保持非零元素的相对顺序不变。比如 [0,1,0,3,12] 要变成 [1,3,12,0,0]。两个约束缺一不可:不能复制数组(必须原地)、非零的先后次序不能乱。这直接排除了「新开数组先拷非零再补零」的写法——它顺序对,但不是原地,还白花 O(n) 额外空间。
为什么想到快慢指针
观察目标形态:结果数组就是「前段按原序排好的非零区 + 后段全零区」。既然如此,只需要两个角色分工:快指针 fast 负责从左到右找非零,慢指针 slow 负责记住「非零区右侧的第一个空位」。fast 扫到非零就把它和 slow 位置交换,slow 前进一格;fast 扫到 0 什么都不做。0 不需要被主动搬运——非零元素一个个被换到前面时,0 自然被换去了后面。
算法全程保持的不变量是什么
循环过程中有两条性质始终成立:slow 左边的区域全是非零且保持原始相对顺序;slow 到 fast 之间(若非空)全是 0。理解了这两条就能看懂交换那一步为什么对:fast 遇到非零时,slow 位置上要么是 0(两指针已经错开),要么就是 fast 自己(前面还没出现过 0)。前者把非零换进空位、把 0 甩到后面;后者是原地自换、无副作用。两种情况都让不变量延续到下一轮,扫完全数组时不变量就是最终答案的形态。
为什么交换法不会打乱非零的顺序
fast 从左到右按序发现非零元素,slow 接收它们的位置也从左到右递增:第一个被发现的非零落在最前面,第二个落在第二格,依此类推。非零进入前段的次序就是它们在原数组中的次序,相对顺序自然保持。这也是它优于「遇到 0 再往后找一个非零来填」的原因——那种写法内层还要再扫一遍找非零,最坏退化成 O(n²),而快慢指针让每个元素只被 fast 看一次。
复杂度与边界情况
时间 O(n):fast 扫一遍数组,slow 至多也走一遍,每步只做常数次比较和交换。空间 O(1):只用两个下标变量。极端输入都能自然兜住:全非零时每次交换都是自换,数组不变;全零时 slow 一直停在 0、从不交换。唯一要盯住的实现细节是 slow 只在放下一个非零之后才前进,遇到 0 时绝不能动——slow 一旦跟着 0 走,「指向下一个空位」的含义就被破坏,后面的交换会放错位置。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条「慢指针守住非零区末尾、快指针找到非零就和慢指针位置交换,零自然被甩到后面」,下面每帧都在套它。
快指针 fast 在下标 0 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 0。零就这样被留在后面、等着被后来的非零换走。
快指针 fast 走到下标 1,这里是非零的 1。慢指针 slow 此刻守在下标 0(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
交换完成:非零值 1 落到了 slow=0(绿色非零区又长一格),它原来在下标 1 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
非零 1 既已归位到下标 0,慢指针 slow 就前进一格,到下标 1,继续守住非零区右侧的下一个空位,等下一个非零来填。
快指针 fast 在下标 2 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 1。零就这样被留在后面、等着被后来的非零换走。
快指针 fast 走到下标 3,这里是非零的 3。慢指针 slow 此刻守在下标 1(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
交换完成:非零值 3 落到了 slow=1(绿色非零区又长一格),它原来在下标 3 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
非零 3 既已归位到下标 1,慢指针 slow 就前进一格,到下标 2,继续守住非零区右侧的下一个空位,等下一个非零来填。
快指针 fast 走到下标 4,这里是非零的 12。慢指针 slow 此刻守在下标 2(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
交换完成:非零值 12 落到了 slow=2(绿色非零区又长一格),它原来在下标 4 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
非零 12 既已归位到下标 2,慢指针 slow 就前进一格,到下标 3,继续守住非零区右侧的下一个空位,等下一个非零来填。
快指针 fast 在下标 5 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 3。零就这样被留在后面、等着被后来的非零换走。
快指针 fast 走到下标 6,这里是非零的 5。慢指针 slow 此刻守在下标 3(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
交换完成:非零值 5 落到了 slow=3(绿色非零区又长一格),它原来在下标 6 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
非零 5 既已归位到下标 3,慢指针 slow 就前进一格,到下标 4,继续守住非零区右侧的下一个空位,等下一个非零来填。
快指针 fast 在下标 7 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 4。零就这样被留在后面、等着被后来的非零换走。
快指针 fast 走到下标 8,这里是非零的 8。慢指针 slow 此刻守在下标 4(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
交换完成:非零值 8 落到了 slow=4(绿色非零区又长一格),它原来在下标 8 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
非零 8 既已归位到下标 4,慢指针 slow 就前进一格,到下标 5,继续守住非零区右侧的下一个空位,等下一个非零来填。
fast 扫完整趟:前 5 个位置(绿色)是按原序排好的非零元素 [1, 3, 12, 5, 8],后面全被换成了 0。结果 [1, 3, 12, 5, 8, 0, 0, 0, 0]。两个指针各只走一遍,O(n) 时间、O(1) 额外空间。
边界先想清:全 0 时 slow 不动、全非零时每步自己和自己交换,都正确。
两个高频追问,区分原地交换 O(1) 与朴素 O(n) 空间解,并解释「为什么顺序不乱」。
参考代码
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 # 慢指针前进 # 原地完成, 无返回值复杂度
- 时间:O(n),fast 扫一遍,slow 最多走一遍
- 空间:O(1),只用两个指针,原地交换,不开新数组
易错点
面试追问把动画讲成自己的话
追问如果只要求「把非零搬到前面」而不在意原地,最简单怎么写?
追问为什么交换法能保持非零元素的相对顺序?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
颜色分类
LeetCode 75 · 中等 · 沿着 双指针 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题