📘 題目敘述
給定一個整數陣列 nums,請將所有的 0 移動到陣列的最後方,同時維持非零元素的相對順序。
注意:
- 必須在不複製陣列的情況下就地(in-place)完成操作。
條件限制
1 ≤ nums.length ≤ 10^4-2^31 ≤ nums[i] ≤ 2^31 - 1
🧠 解題思路(一)(直覺想法)
我的想法是把這題拆成兩個非常直觀的步驟來處理:
- 先把所有非 0 的數字依照原本順序收集起來
- 再把出現過的 0 全部接在後面
這樣可以很清楚地確保:
- 非零元素的相對順序不會被改變
- 所有的 0 都會集中在陣列最後
所有變數(對應程式碼)
n:原始陣列nums的長度zeros:紀錄陣列中0出現的次數ans:暫時存放「非零元素 + 最後補 0」的新陣列
🪜 主要流程步驟
步驟一:掃描整個陣列,分離 0 與非 0
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
zeros++;
} else {
ans.push_back(nums[i]);
}
}
這個迴圈做的事情是:
- 如果遇到
0,就只記錄次數(zeros++) - 如果不是
0,就依照原本順序放進ans
結束後:
ans裡面只會有所有非零元素(順序不變)zeros則記錄了後面需要補幾個0
步驟二:把所有的 0 接到後面
for (int j = 0; j < zeros; j++) {
ans.push_back(0);
}
根據剛剛記錄的 zeros 次數,將對應數量的 0 接到 ans 的尾端。
這一步完成後,ans 就已經符合題目要求的排列結果。
步驟三:更新原陣列
nums = ans;
將整理好的結果直接指定回 nums,等同於完成「把所有 0 移到最後」的操作。
為什麼這樣做是正確的?
題目要求非零元素的相對順序不能改變,我的做法是「先照順序收集非零,再補 0」,不需要交換、不會打亂順序。
💻 程式碼實作
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size();
int zeros = 0;
vector<int> ans;
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
zeros++;
} else {
ans.push_back(nums[i]);
}
}
for (int j = 0; j < zeros; j++) {
ans.push_back(0);
}
nums = ans;
}
};
🧠 解題思路(二)(Two Pointers 原地優化)
在原本的解法中,我使用了一個額外的陣列來存放結果,雖然邏輯清楚,但需要額外的 O(n) 空間。
後來我將作法改成 Two Pointers 的原地處理方式,在不使用額外陣列的情況下,直接在 nums 本身完成操作。
新增指標 pos:記錄下一個非 0 要放的位置
pos表示目前應該放「下一個非 0 元素」的索引位置- 一開始設為
0,代表從陣列最前面開始放
第一階段:把所有非 0 元素往前搬
for (int i = 0; i < n; i++) {
if (nums[i] != 0) {
nums[pos] = nums[i];
pos++;
}
}
這個迴圈的邏輯是:
- 用
i從左到右掃描整個陣列 - 只要遇到非 0 的數字,就把它放到
nums[pos],然後pos++
這樣可以保證:
- 所有非 0 元素會依照原本順序集中在陣列前半段
pos最後停在「非 0 區段的結尾」
第二階段:把後面的空位全部補成 0
while (pos < nums.size()) {
nums[pos] = 0;
pos++;
}
前半段已經放好所有非 0 元素,剩下的位置一定都應該是 0,直接補 0 到陣列結尾即可。
為什麼這樣改比較好?
- 不需要額外的陣列(空間複雜度從
O(n)→O(1)) - 非 0 元素的相對順序完全不會被破壞
- 邏輯仍然是「先處理非 0,再補 0」,和原本想法一致
💻 程式碼實作
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size();
int pos = 0;
for (int i = 0; i < n; i++) {
if (nums[i] != 0) {
nums[pos] = nums[i];
pos++;
}
}
while (pos < nums.size()) {
nums[pos] = 0;
pos++;
}
}
};
🔗 題目連結
https://leetcode.com/problems/move-zeroes/