【码道初阶】LeetCode283移动零:把数组当成三段区间,用双指针原地“分区+稳定”

283 移动零:把数组当成三段区间,用双指针原地"分区+稳定"

题目要求很明确:

  • 把所有 0 挪到数组末尾
  • 非零元素的相对顺序不能变(稳定)
  • 必须原地操作,不能额外复制数组

我一看到"原地 + 稳定 + 把某一类元素挪到一边",脑子里就会自动弹出一个关键词:数组分区(partition)

不同于快排那种不稳定分区,这题还要求稳定,所以需要一种"稳定分区"的写法。


1. 把问题翻译成"区间维护"

来看这段代码之前,我先给数组划三个区间(这是这题最关键的建模):

  • [0, dest]:已经整理好的 非零区
  • [dest+1, cur-1]:已经扫过但被挤到后面的 零区
  • [cur, n-1]:还没处理的 待处理区

只要我能保证这三个区间的定义始终成立,那么当 cur 扫完整个数组时:

  • [0, dest] 全是非零,且顺序稳定
  • 后面自然就是一堆零

这就完成题目要求。


2. 双指针各自扮演什么角色?

这段代码里两个指针:

  • cur:扫描指针,从左到右看每一个元素
  • dest:非零区的"最后位置"(也可以理解为"下一个非零该放到哪里 - 1")

初始化写成:

  • cur = 0
  • dest = -1

dest = -1 的好处是:当第一个非零出现时,先 dest++ 就正好落在 0 下标。


3. 核心动作:遇到非零就把它"放到非零区末尾"

来看这段代码的核心部分:

java 复制代码
if(nums[cur] != 0){
    dest++;
    int tmp = nums[cur];
    nums[cur] = nums[dest];
    nums[dest] = tmp;
}

这里的逻辑很像"稳定地把非零元素往前收集":

  • nums[cur] 是非零:说明它应该进入非零区
  • dest++:把非零区的边界扩展一格
  • 交换 nums[cur]nums[dest]:把这个非零放到非零区末尾

为什么交换不会破坏相对顺序?

关键在于:cur 是从左到右扫的。

每次遇到一个非零元素,它都会被放到 dest 的下一个位置。也就是说:

  • 先遇到的非零会先占据更靠前的位置
  • 后遇到的非零只会放在更靠后的位置

因此非零元素的相对顺序天然保持不变。

这就满足了"稳定"的要求。


4. 为什么这算"尽量减少操作次数"?

这题进阶问:能不能尽量减少操作?

这份写法的交换次数等于"非零元素的个数"。更准确地说:

  • cur == dest 时,交换的是自己和自己,其实可以优化掉(属于微优化)
  • 但即使不优化,时间复杂度还是 O(n),在面试和题解里完全可接受

如果想进一步减少无意义交换,可以加一个判断:

java 复制代码
if (cur != dest) swap(...)

不过不加也不会影响正确性。


5. 用示例走一遍,区间感会更清楚

数组:[0,1,0,3,12]

  • 初始:dest=-1
  • cur=0,numscur=0 → 跳过
    非零区为空
  • cur=1,numscur=1 非零 → dest=0,交换 nums1 和 nums0
    数组变 [1,0,0,3,12]
  • cur=2,numscur=0 → 跳过
  • cur=3,numscur=3 非零 → dest=1,交换 nums3 和 nums1
    数组变 [1,3,0,0,12]
  • cur=4,numscur=12 非零 → dest=2,交换 nums4 和 nums2
    数组变 [1,3,12,0,0]

结束:非零区 [0..2],零自然在末尾。


6. 完整可用代码(Java)

来看这段代码,就是完整的解法:

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {
        // 数组分组思想:用双指针维护区间
        // [0, dest]     : 已整理好的非零元素区(稳定)
        // [dest+1, cur-1]: 已扫描过的零元素区
        // [cur, n-1]     : 待处理区
        for (int cur = 0, dest = -1; cur < nums.length; cur++) {
            if (nums[cur] != 0) {
                dest++;
                int tmp = nums[cur];
                nums[cur] = nums[dest];
                nums[dest] = tmp;
            }
        }
    }
}

总结:这题真正想考的"套路"

  • 把数组划成区间,维护一个清晰不变量
  • cur 负责扫全数组
  • dest 负责标记"非零区的末尾"
  • 遇到非零就塞到非零区末尾,顺序自然稳定
  • 扫完一遍,零就被挤到后面了

这套"稳定分区"思路非常通用:以后遇到"把某类元素挪到一侧、保持相对顺序、原地操作"的题,十有八九都能用类似的双指针区间法解决。

相关推荐
To_OC6 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
旖-旎7 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
To_OC7 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn8 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹8 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
雪碧聊技术9 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划
延凡科技11 小时前
多场景落地复盘:端边云架构无人机智能巡检系统设计与实践
大数据·数据结构·人工智能·科技·架构·无人机·能源
CV-Climber11 小时前
检索技术的实际应用
人工智能·算法
hhzz12 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型