【码道初阶】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,nums[cur]=0 → 跳过
    非零区为空
  • cur=1,nums[cur]=1 非零 → dest=0,交换 nums[1] 和 nums[0]
    数组变 [1,0,0,3,12]
  • cur=2,nums[cur]=0 → 跳过
  • cur=3,nums[cur]=3 非零 → dest=1,交换 nums[3] 和 nums[1]
    数组变 [1,3,0,0,12]
  • cur=4,nums[cur]=12 非零 → dest=2,交换 nums[4] 和 nums[2]
    数组变 [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 负责标记"非零区的末尾"
  • 遇到非零就塞到非零区末尾,顺序自然稳定
  • 扫完一遍,零就被挤到后面了

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

相关推荐
ytttr87319 小时前
基于MATLAB的一维对流扩散方程数值求解
开发语言·算法·matlab
qq_225891746619 小时前
基于Python+Django豆瓣图书数据可视化分析推荐系统 可视化 协同过滤算法 情感分析 爬虫
爬虫·python·算法·信息可视化·数据分析·django
one____dream20 小时前
【算法】移除链表元素与反转链表
数据结构·python·算法·链表
memmolo20 小时前
【3D测量中的术语:系统误差、随机误差、精密度、准确度】
算法·计算机视觉·3d
睡不醒的kun20 小时前
不定长滑动窗口-基础篇(2)
数据结构·c++·算法·leetcode·哈希算法·散列表·滑动窗口
霑潇雨20 小时前
题解 | 分析每个商品在不同时间段的销售情况
数据库·sql·算法·笔试
金枪不摆鳍20 小时前
算法-动态规划
算法·动态规划
季明洵20 小时前
Java中哈希
java·算法·哈希
jaysee-sjc20 小时前
【练习十】Java 面向对象实战:智能家居控制系统
java·开发语言·算法·智能家居
cici1587420 小时前
基于MATLAB实现eFAST全局敏感性分析
算法·matlab