精选专栏链接 🔗
欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰
更多内容持续更新中~
【LeetCode 热题 100】移动零
📝题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序 。请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
bash
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
bash
输入: nums = [0]
输出: [0]
进阶:你能尽量减少完成的操作次数吗?
💡提示信息
1 <= nums.length <= 10 4 10^4 104;
− 2 31 -2^{31} −231 <= numsi <= 2 31 2^{31} 231 - 1
在处理数组类算法题时,"原地修改"往往是面试中的高频考点。
虽然题目看似简单,但如果深究起来,它其实考察了我们对数组操作效率的理解。今天,我将带大家深入剖析这道题的两种主流解法:两次遍历法和双指针交换法。
解法一:两次遍历法
这是最容易想到且符合题目要求的解法。、核心思想是:既然我们要把 0 扔到最后,那不如先把非 0 的数字按顺序排好,剩下的位置自然就是给 0 的。
算法思路
- 可以定义一个指针 j,用来记录当前"非零元素"应该存放的位置;
- 第一遍遍历(整理非零元素):遍历数组,每当发现一个
非零元素numsi,就把它赋值给 numsj,然后 j 向后移动一位。这样遍历完后,数组的前 j 个位置就紧凑地排满了所有的非零元素; - 第二遍遍历(补零):此时,从索引 j 开始直到数组末尾的所有位置,原本要么是 0,要么是被我们移走的非零元素的旧位置。我们只需要把这些位置全部赋值为 0 即可;
Java代码实现如下
java
class Solution {
public void moveZeroes(int[] nums) {
if(nums==null|| nums.length == 0){
return;
}
int j = 0; // 记录非零元素应该存放的位置
// 第一次遍历:将所有非零元素按顺序移到数组前部
for(int i = 0; i<nums.length; i++ ){
if(nums[i]!=0){
nums[j] = nums[i];
j++;
}
}
// 第二次遍历:将 j 之后的所有位置填充为 0
while(j < nums.length){
nums[j] = 0;
j++;
}
}
}
提交代码,运行结果如下

复杂度分析
- 时间复杂度O(n) 。 虽然有两个循环,但它们不是嵌套的,而是顺序执行的。第一个循环遍历 n 次,第二个循环最多遍历 n 次,总体是线性的。
- 空间复杂度O(1) 。 我们只需要常数级别的额外空间来存储指针 j,完全符合原地操作的要求。
解法二:双指针法
题目中的进阶要求提到:"尽量减少完成的操作次数"。上面的解法虽然好,但在处理全是非零元素的数组时,依然会进行大量的重复赋值操作。
双指针交换法利用"交换"的技巧,能在一次遍历中完成任务,且减少了不必要的写操作。
算法思路
我们定义两个指针:
- 左指针 left: 指向下一个非零数应该去的地方,或者说是第一个 0 的位置;
- 右指针 right: 指向当前处理到的元素,负责不断向右探索;
- 如果 numsright 是 0,说明这个数不需要动,right 继续向右走;
- 如果 numsright 不是 0,说明我们找到了一个需要移动的数。此时,我们将 numsright 和 numsleft 进行交换,交换后,left 向右移动一位,准备接收下一个非零数。
Java代码实现如下
java
class Solution {
public void moveZeroes(int[] nums) {
if(nums==null|| nums.length == 0){
return;
}
int left = 0; // 左指针,指向非零元素应该放置的位置
// 右指针 right 负责遍历
for(int right = 0; right<nums.length; right++ ){
// 如果当前元素不为 0,则与 left 指针处的元素交换
if(nums[right]!=0){
swap(nums,right,left);
left++;
}
}
return;
}
// 辅助函数:交换数组中两个位置的元素
private void swap(int[] nums, int right, int left){
if(right != left){ // 优化:如果是同一个位置,不需要交换
int temp = nums[right];
nums[right]=nums[left];
nums[left]=temp;
}
}
}
最终可以实现如下效果:
如果数组全是非零数,left 和 right 会一起走,可以不交换,无副作用;如果遇到了 0,left 会停在 0 的位置,等待 right 找到后面的非零数来和它交换。这样既把非零数提到了前面,又把 0 换到了后面。
提交代码,运行结果如下

复杂度分析
- 时间复杂度O(n) 。只进行了一次遍历;
- 空间复杂度O(1) 。仅使用了常数空间;
双指针交换法只有当 left 和 right 指向不同元素且均为非零时才发生交换操作(交换涉及 3 次赋值)。如果数组中没有 0,该算法几乎没有额外的写操作。因此,双指针法在操作次数上通常更优。
总结对比
| 特性 | 两次遍历 (覆盖+补零) | 双指针交换 (一次遍历) |
|---|---|---|
| 核心逻辑 | 先覆盖非零数,再把剩余位置填 0 | 遇到非零数就和前面的 0 交换 |
| 遍历次数 | 2 次 | 1 次 |
| 时间复杂度 | O(n) | O(n) |
| 空间复杂度 | O(1) | O(1) |
| 代码复杂度 | 简单直观 | 需要理解指针交互 |