移动零 图解题解
把零全赶到末尾、非零顺序不变、原地完成——fast 扫到非零就和 slow 互换,一趟解决。
像整理书架:slow 是下一个空位,fast 从左往右挑非零元素,遇到就跟 slow 位置上的数对调、slow 往右挪一格。fast 按原顺序捡起非零,依次放到最前面,书架右端自然就剩下所有被换下来的零——不用移动整段、一趟搞定。
这道题到底在问什么
- 输入
- nums = [0,1,0,3,12]
- 输出
- [1,3,12,0,0]
最优解:为什么这么做
一句话答案: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 走,「指向下一个空位」的含义就被破坏,后面的交换会放错位置。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这条「慢指针守住非零区末尾、快指针找到非零就和慢指针位置交换,零自然被甩到后面」,下面每帧都在套它。
- 4快指针 fast 在下标 0 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 0。零就这样被留在后面、等着被后来的非零换走。
- 5快指针 fast 走到下标 1,这里是非零的 1。慢指针 slow 此刻守在下标 0(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
- 6交换完成:非零值 1 落到了 slow=0(绿色非零区又长一格),它原来在下标 1 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
- 7非零 1 既已归位到下标 0,慢指针 slow 就前进一格,到下标 1,继续守住非零区右侧的下一个空位,等下一个非零来填。
- 8快指针 fast 在下标 2 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 1。零就这样被留在后面、等着被后来的非零换走。
- 9快指针 fast 走到下标 3,这里是非零的 3。慢指针 slow 此刻守在下标 1(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
- 10交换完成:非零值 3 落到了 slow=1(绿色非零区又长一格),它原来在下标 3 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
- 11非零 3 既已归位到下标 1,慢指针 slow 就前进一格,到下标 2,继续守住非零区右侧的下一个空位,等下一个非零来填。
- 12快指针 fast 走到下标 4,这里是非零的 12。慢指针 slow 此刻守在下标 2(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
- 13交换完成:非零值 12 落到了 slow=2(绿色非零区又长一格),它原来在下标 4 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
- 14非零 12 既已归位到下标 2,慢指针 slow 就前进一格,到下标 3,继续守住非零区右侧的下一个空位,等下一个非零来填。
- 15快指针 fast 在下标 5 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 3。零就这样被留在后面、等着被后来的非零换走。
- 16快指针 fast 走到下标 6,这里是非零的 5。慢指针 slow 此刻守在下标 3(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
- 17交换完成:非零值 5 落到了 slow=3(绿色非零区又长一格),它原来在下标 6 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
- 18非零 5 既已归位到下标 3,慢指针 slow 就前进一格,到下标 4,继续守住非零区右侧的下一个空位,等下一个非零来填。
- 19快指针 fast 在下标 7 看到的是 0。0 不需要归位,所以 fast 直接右移一格继续找;慢指针 slow 不动,仍守在下标 4。零就这样被留在后面、等着被后来的非零换走。
- 20快指针 fast 走到下标 8,这里是非零的 8。慢指针 slow 此刻守在下标 4(非零区右边第一个空位)。准备把这个非零值和 slow 处的值交换,让非零归位。
- 21交换完成:非零值 8 落到了 slow=4(绿色非零区又长一格),它原来在下标 8 的位置换来一个 0。这一步只是把值换好,慢指针还没动。
- 22非零 8 既已归位到下标 4,慢指针 slow 就前进一格,到下标 5,继续守住非零区右侧的下一个空位,等下一个非零来填。
- 23fast 扫完整趟:前 5 个位置(绿色)是按原序排好的非零元素 [1, 3, 12, 5, 8],后面全被换成了 0。结果 [1, 3, 12, 5, 8, 0, 0, 0, 0]。两个指针各只走一遍,O(n) 时间、O(1) 额外空间。
⚠️ 容易写错的地方
✗ 错:先把非零拷到新数组再补零
✓ 对:原地交换
题目要求原地操作,开新数组不符合(也多用 O(n) 空间)
✗ 错:遇到 0 就和后面非零交换、但用嵌套循环找
✓ 对:快慢指针一趟
嵌套找退化成 O(n²),快慢指针一趟 O(n) 就够
✗ 错:交换后还让 slow 跟着 0 走
✓ 对:slow 只在放下非零后前进
slow 必须始终指向「下一个非零该放的空位」,遇 0 不动
完整代码(Python / C++ / Java)
Python
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 # 慢指针前进
# 原地完成, 无返回值C++
void moveZeroes(vector<int>& nums){
int slow = 0; // 下一个非零该放的位置
for(int fast = 0; fast < nums.size(); fast++){
if(nums[fast] != 0){ // 找到非零
swap(nums[slow], nums[fast]);
slow++; // 慢指针前进
}
}
}Java
public void moveZeroes(int[] nums) {
int slow = 0; // 下一个非零该放的位置
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != 0) { // 找到非零
int t = nums[slow]; // 交换 slow 与 fast
nums[slow] = nums[fast];
nums[fast] = t;
slow++; // 慢指针前进
}
}
}复杂度
时间
O(n)
fast 扫一遍,slow 最多走一遍
空间
O(1)
只用两个指针,原地交换,不开新数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 移动零 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果只要求「把非零搬到前面」而不在意原地,最简单怎么写?+
另开一个数组,先把所有非零按序拷过去,再补 0 到原长度。逻辑最直观但用 O(n) 额外空间、且不是原地,面试要求原地时不能用。
为什么交换法能保持非零元素的相对顺序?+
fast 是从左到右顺序扫的,每次把当前非零换到 slow(也是从左到右递增的位置),所以非零元素进入非零区的先后顺序就是它们原本的先后顺序,相对顺序不变。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 移动零 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。