Move Zeroes
題目描述
給定整數陣列 nums,將所有 0 移動到陣列末尾,同時保持非零元素的相對順序。必須原地操作。
輸入:nums = [0,1,0,3,12]
輸出:[1,3,12,0,0]
輸入:nums = [0]
輸出:[0]
解題思路
初始化
slow = 0,指向下一個非零元素應該放置的位置
遍歷
fast 用 for 迴圈從 0 到 n-1 逐一掃描每個元素
搬移
若 nums[fast] != 0,執行 swap(nums[slow], nums[fast]),然後 slow++。交換把非零值搬到前面,零被推到後面
結束
遍歷完成後,nums[0..slow-1] 全是非零且保持原順序,nums[slow..n-1] 全是零
程式碼
C#
public class Solution {
public void MoveZeroes(int[] nums) {
int slow = 0;
for (int fast = 0; fast < nums.Length; fast++) {
if (nums[fast] != 0) {
// 交換:非零元素往前搬
(nums[slow], nums[fast]) = (nums[fast], nums[slow]);
slow++;
}
}
}
}TypeScript
function moveZeroes(nums: number[]): void {
let slow = 0;
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] !== 0) {
[nums[slow], nums[fast]] = [nums[fast], nums[slow]];
slow++;
}
}
}Python
def moveZeroes(nums: list[int]) -> None:
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1複雜度分析
| | Time | Space | |--|------|-------| | 本解法 | O(n) | O(1) |
fast 從 0 到 n-1 只走一遍 O(n),每步做常數次操作。原地交換不需額外陣列,空間 O(1)。
邊界情況
- 沒有零:例如
[1,2,3],slow 和 fast 同步前進,每次交換等於自己跟自己交換,陣列不變 - 全是零:例如
[0,0,0],fast 掃完都不觸發交換,slow 停在 0,陣列不變 - 只有一個元素:
[0]或[1],迴圈只跑一次,結果不變
圖解
圖表展示交換過程:fast=0 時值為 0 跳過,fast=1 時值為 1 與 slow=0 交換。fast=2 時值為 0 跳過,fast=3 時值為 3 與 slow=1 交換。每次交換後 slow 前進一格,零被自然推到後方。
進階觀點
💡面試追問
-
Q: 為什麼用交換而不是直接覆蓋再補零? A: 兩種都可以,都是 O(n)。覆蓋法需要兩次遍歷:第一次把非零值依序寫入
nums[0..slow-1],第二次把nums[slow..n-1]全部填 0。交換法只需一次遍歷,因為 swap 同時完成「搬前」和「補零」兩個動作,操作次數更少。 -
Q: 如果要保持零的位置不變,移動非零元素? A: 題意完全不同。需要找出零的位置形成「空隙」,然後把非零值重新排列填入非空隙位置。本質上變成 partition 問題,需要額外記錄零的位置。
-
Q: 快慢指針模式還能用在哪? A: LeetCode 27(Remove Element)用相同框架移除指定值。LeetCode 26(Remove Duplicates from Sorted Array)用 slow 追蹤不重複的寫入位置。鏈表中快慢指針用於找中點(slow 走一步、fast 走兩步)和 Floyd 環檢測。
理解測驗
🤔 為什麼交換而非覆蓋更優雅?
🤔 slow 指針代表什麼含義?