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

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

相关推荐
s090713612 小时前
FPGA加速:Harris角点检测全解析
图像处理·算法·fpga开发·角点检测
前端程序猿之路12 小时前
30天大模型学习之Day 2:Prompt 工程基础系统
大数据·人工智能·学习·算法·语言模型·prompt·ai编程
星火开发设计12 小时前
堆排序原理与C++实现详解
java·数据结构·c++·学习·算法·排序算法
2501_9418036212 小时前
在柏林智能城市照明场景中构建实时调控与高并发能耗数据分析平台的工程设计实践经验分享
算法
CoderIsArt12 小时前
常用SCSI数据结构的详细注释和用法
数据结构
福楠12 小时前
C++ STL | list
c语言·开发语言·数据结构·c++·算法·list
努力学算法的蒟蒻12 小时前
day55(1.6)——leetcode面试经典150
算法·leetcode·面试
s砚山s12 小时前
代码随想录刷题——二叉树篇(十)
算法
2301_7644413312 小时前
基于HVNS算法和分类装载策略的仓储系统仿真平台
人工智能·算法·分类
AI科技星12 小时前
统一场论变化的引力场产生电磁场推导与物理诠释
服务器·人工智能·科技·线性代数·算法·重构·生活