
把 nums 当作栈
最后,在栈的末尾添加两个 0,即为答案 [1,3,12,0,0]。
为了做到 O(1) 空间复杂度,直接把 nums 当作栈,用一个变量 stackSize 表示栈的大小,初始值为 0。
入栈就是把 nums[stackSize] 置为 nums[i],然后把 stackSize 加一。
最后把 nums 中的下标从 stackSize 到 n−1 的数都置为 0。
一句话思路:用一个栈记录非零元素。将nums原地当作栈,将不为0的元素入栈,最后补0到length就行了
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
// 一句话思路:用一个栈记录非零元素。将nums原地当作栈,将不为0的元素入栈,最后补0到length就行了
var moveZeroes = function (nums) {
let stackSize = 0
for (const n of nums) {
if (n) {
nums[stackSize++] = n
}
}
for (let i = stackSize; i < nums.length; i++) {
nums[i] = 0
}
}方法二:双指针 + 交换元素
方法一(常规双指针赋值补 0)在最坏情况下(nums 全为 0),需要遍历 nums 两次。双指针 + 交换元素可实现一次遍历完成操作,是更优解。
核心思路
将数组中的 0 视作空位,核心目标是把所有非零元素依次移到数组左侧的空位上,且保证非零元素相对顺序不变。
- 例如
nums=[0,0,1,2],将最左侧空位与非零元素1交换,数组变为[1,0,0,2];原1的位置成为新空位,后续继续将下一个非零元素2与新的最左侧空位交换即可。 - 核心是维护最左边空位的下标,确保非零元素按顺序填充。
具体思路
-
定义两个指针:遍历指针
i(从左到右遍历数组,初始值0)、空位指针i0(指向数组中最左侧的空位,初始值0)。 -
保证下标区间 [i0, i-1] 内都是空位(即 0),这是整个算法的核心性质。
-
遍历过程中分两种情况:
- 若
nums[i] ≠ 0:将当前非零元素与最左侧空位交换(swap(nums[i], nums[i0])),交换后i0和i均加 1(原空位被填充,新空位右移,遍历指针继续前进)。 - 若
nums[i] = 0:无需交换,仅将遍历指针i加 1(当前位置为新空位,空位指针保持不变)。
- 若
算法本质
通过一次遍历,让非零元素依次 “填充” 左侧的 0 空位,交换操作既完成了非零元素的左移,又让原非零元素位置变为新空位,全程保证非零元素的相对顺序,且仅需一次遍历即可完成所有操作。
保证下标区间 [i0,i−1] 都是空位,且 i0 指向最左边的空位。
用i去找对的数(非0数),用i0来维持对的位置(0)
var moveZeroes = function (nums) {
let i0 = 0
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) {
;[nums[i], nums[i0]] = [nums[i0], nums[i]]
i0++
}
}
}