LeetCode 热题 283. 移动零:原地操作的优雅解法
一、题目理解:需求很明确
题目要求:
- 把数组里的所有
0移到末尾 - 保持非零元素的相对顺序
- 必须原地操作(不能复制新数组)
比如输入[0,1,0,3,12],输出得是[1,3,12,0,0];如果输入本身就是[0],直接返回[0]就行。
二、我的解题思路:两步走策略
一开始我想的是「双指针交换」,但后来发现更简洁的是 **"先填非零,再补零"** 的思路:
解法核心逻辑
-
第一步:把非零元素 "往前搬" 用一个变量
stack_size记录 "非零元素应该放的位置",遍历数组时,遇到非零元素就放到nums[stack_size],然后stack_size自增。(相当于把所有非零元素按顺序 "堆" 在数组前半部分) -
第二步:给剩下的位置补 0 遍历结束后,从
stack_size的位置开始,把数组剩下的部分全填成 0。
代码实现(C++)
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int stack_size = 0;
// 第一步:填充非零元素
for (int x : nums) {
if (x) {
nums[stack_size++] = x;
}
}
// 第二步:补0
fill(nums.begin() + stack_size, nums.end(), 0);
}
};
三、为什么这个解法高效?
- 时间复杂度 O (n):只遍历数组两次(一次填非零,一次补零),n 是数组长度。
- 空间复杂度 O (1):完全原地操作,没额外开数组。
- 操作次数少:比起 "遇到 0 就交换" 的写法,减少了不必要的交换操作(比如连续多个 0 的情况)。
四、拓展:双指针交换法(另一种思路)
其实也可以用双指针 实现(比如left指针指向 "下一个非零元素该放的位置"):
cpp
void moveZeroes(vector<int>& nums) {
int left = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != 0) {
swap(nums[left], nums[i]);
left++;
}
}
}
这个写法更 "紧凑",但本质和 "填非零 + 补零" 是一个逻辑 ------ 只是把 "赋值" 换成了 "交换"~