题目描述
给定一个数组
nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。注意:
必须在不复制数组 的情况下原地对数组进行操作。
进阶:尽量减少完成的交互次数。
解法一:辅助数组法(不符合原地要求)
这是最直观的解法,思路是创建一个新的数组,将非零元素依次填入,剩余位置补零,最后再拷贝回原数组。
代码:
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int length1 = nums.size();
// 创建了辅助数组 a,空间复杂度 O(N)
vector<int> a(nums.size());
int num = 0;
// 第一次遍历:将非零元素放入辅助数组
for(int i = 0; i < nums.size(); i++) {
if(nums[i] != 0) {
a[num] = nums[i];
num++;
}
}
// 第二次遍历:将辅助数组剩余位置补零
for(int i = num; i < length1; i++) {
a[i] = 0;
}
// 第三次遍历:将辅助数组拷贝回原数组
for(int i = 0; i < nums.size(); i++) {
nums[i] = a[i];
}
return;
}
};

复杂度分析:
时间复杂度:O(N)。进行了三次遍历。
空间复杂度 :O(N)。开辟了与原数组等大的辅助空间
a。不足 :题目明确要求原地操作(In-place),因此该解法虽然逻辑正确,但空间复杂度不符合题目进阶要求,面试中通常会被要求优化。
解法二:双指针法(最优解)
为了满足"原地"且"减少操作次数"的要求,我们可以使用双指针(快慢指针)。
核心思想
使用两个指针:
slow:指向当前已经处理好的非零序列的末尾(即下一个非零元素应该放置的位置)。
fast:遍历整个数组。
算法流程:
fast从左向右遍历。当
nums[fast] != 0时,交换nums[slow]和nums[fast],然后slow向前移动一位。当
nums[fast] == 0时,不做处理,fast继续前进。
这样,slow 左边始终是非零元素,且相对顺序保持不变;slow 到 fast 之间全是 0。通过交换,直接将 0 换到了后面。
正确代码实现
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0;
int n = nums.size();
// fast 为快指针,遍历数组
for (int fast = 0; fast < n; fast++) {
// 当遇到非零元素时,与 slow 指针位置交换
if (nums[fast] != 0) {
swap(nums[slow], nums[fast]);
slow++;
}
}
}
};

复杂度分析:
时间复杂度:O(N)。只需遍历一次数组。
空间复杂度:O(1)。仅使用了常数个变量,满足原地操作要求。
操作次数:每个非零元素最多被交换一次,相比辅助数组法减少了赋值操作。
总结与对比
| 维度 | 辅助数组法 | 双指针法 (最优解) |
|---|---|---|
| 空间复杂度 | O(N) - 需要额外数组 | O(1) - 原地操作 |
| 时间复杂度 | O(N) - 遍历3次 | O(N) - 遍历1次 |