Skip to content

题目链接 · 灵神原题解(署名来源)

方法一:把 nums 当作栈

用一个栈记录非零元素。

看示例 1,nums=[0,1,0,3,12]

inums[i]nums[i] 是否入栈
00[]
11[1]
20[1]
33[1,3]
412[1,3,12]

最后,在栈的末尾添加两个 0,即为答案 [1,3,12,0,0]

为了做到 O(1) 空间复杂度,直接把 nums 当作栈,用一个变量 stackSize 表示栈的大小,初始值为 0

入栈就是把 nums[stackSize] 置为 nums[i],然后把 stackSize 加一。

最后把 nums 中的下标从 stackSizen1 的数都置为 0

python
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        stack_size = 0
        for x in nums:
            if x:
                nums[stack_size] = x  # 把 x 入栈
                stack_size += 1
        for i in range(stack_size, len(nums)):
            nums[i] = 0
cpp
// C++ 版待补充
python
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        stack_size = 0
        for x in nums:
            if x:
                nums[stack_size] = x  # 把 x 入栈
                stack_size += 1
        nums[stack_size:] = [0] * (len(nums) - stack_size)
cpp
// C++ 版待补充
cpp
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int stack_size = 0;
        for (int x : nums) {
            if (x) {
                nums[stack_size++] = x; // 把 x 入栈
            }
        }
        fill(nums.begin() + stack_size, nums.end(), 0);
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 nnums 的长度。
  • 空间复杂度:O(1)

方法二:双指针+交换元素

方法一在最坏情况下(nums 全为 0),需要遍历 nums 两次。能否做到一次遍历?

核心思路

0 视作空位。我们要把所有非零元素都移到数组左边的空位上,并保证非零元素的顺序不变。

例如 nums=[0,0,1,2],把 1 放到最左边的空位上,数组变成 [1,0,0,2]。注意 1 移动过去后,在原来 1 的位置又产生了一个新的空位。也就是说,我们交换了 nums[0]=0nums[2]=1 这两个数。

为了保证非零元素的顺序不变,我们需要维护最左边的空位的位置(下标)。

具体思路

从左到右遍历 nums[i]

同时维护另一个下标 start0(初始值为 0),并保证下标区间 [start0,i1] 都是空位(0),且 start0 指向最左边的空位。

每次遇到 nums[i]0 的情况,就把 nums[i] 移动到最左边的空位上,也就是交换 nums[i]nums[start0]。交换后把 start0i 都加一,从而使【[start0,i1] 都是空位】这一性质仍然成立。

如果 nums[i]=0,无需交换,只把 i 加一。

示例 1 的 nums=[0,1,0,3,12],计算过程如下(下划线表示交换的两个数):

istart0nums[i]操作后
000不操作
101[1,0,0,3,12]
210不操作
313[1,3,0,0,12]
4212[1,3,12,0,0]

由于每次操作后,[start0,i1] 对应的元素值全为 0 这一性质始终成立,所以 nums 遍历结束后(i=n),[start0,n1] 对应的元素值全为 0,且 [0,start01] 都是交换过去的非零元素,这样就满足了题目「将所有 0 移动到数组的末尾」的要求。

答疑

:如果 nums 的前几个数都不是 0 呢?

start0 会和 i 同时向右移动,直到遇到 0(或者到达数组末尾)为止。

python
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        循环不变量:在循环过程中,nums 的数据分布始终如下图
        [ 非零元素 | 零元素 | 尚未遍历 ]
                    ^       ^
                    start0  i
        """
        start0 = 0
        for i in range(len(nums)):
            if nums[i]:
                nums[i], nums[start0] = nums[start0], nums[i]
                start0 += 1
cpp
// C++ 版待补充
cpp
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        /*
        循环不变量:在循环过程中,nums 的数据分布始终如下图
        [ 非零元素 | 零元素 | 尚未遍历 ]
                    ^       ^
                    start0  i
        */
        int start0 = 0;
        for (int& x : nums) { // 注意 x 是引用
            if (x) {
                swap(x, nums[start0]);
                start0++;
            }
        }
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 nnums 的长度。
  • 空间复杂度:O(1)

专题训练

见下面双指针题单的「§3.4 原地修改」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

本文整理自灵茶山艾府(endlesscheng)的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传