【码道初阶】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 负责标记"非零区的末尾"
  • 遇到非零就塞到非零区末尾,顺序自然稳定
  • 扫完一遍,零就被挤到后面了

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

相关推荐
zwenqiyu20 分钟前
P5283 [十二省联考 2019] 异或粽子题解
c++·学习·算法
wayz1120 分钟前
Momentum:TSI(真实强度指数)技术指标详解
算法·金融·数据分析·量化交易·特征工程
Queenie_Charlie20 分钟前
哈夫曼树
数据结构·c++·哈夫曼树
万事大吉CC1 小时前
Python 笔试输入模板总结
python·算法
lihao lihao1 小时前
Linux信号
开发语言·c++·算法
大白话_NOI1 小时前
【洛谷 P2249】查找(深基 13. 例 1)+ 详细分析
c++·算法
吠品1 小时前
C++实现m行n列带边框的长方形输出
算法
智者知已应修善业1 小时前
【51单片机2个外部中断显示中断历时,初始化8左移3位共阳数码管】2024-6-6
c++·经验分享·笔记·算法·51单片机
西安邮电大学2 小时前
分治算法详细讲解
java·后端·其他·算法·面试
code bean2 小时前
平衡相关性与多样性:推荐系统中的永恒博弈与 MMR 算法详解
算法