【算法拾光】双指针|移动零 + 复写零

题目 1:移动零283. 移动零 - 力扣(LeetCode)

题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

要求:必须在原数组上操作,不能拷贝额外的数组。尽量减少操作次数。

示例:

cpp 复制代码
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

思路分析(快慢指针)

  1. 定义慢指针 slow:指向已经处理好的区域的末尾,存放有效非 0 元素。
  2. 定义快指针 fast:遍历整个数组,寻找非 0 元素。
  3. fast 每找到一个非 0 值,就把这个值赋值给 slow 位置,slow 向后走一步。
  4. 遍历结束后,slow 前面全部是非 0 元素;slow 到数组末尾区间,全部填充 0。

核心思想:slow 负责 "记录有效元素存放位置",fast 负责搜索有效元素。 暴力思路:遇到 0 就整体后移,时间复杂度 O (n²),会超时;双指针 O (n)。

C++ 代码

cpp 复制代码
void moveZeroes(vector<int>& nums) {
    int slow = 0;
    // fast遍历数组,找非0元素
    for(int fast = 0; fast < nums.size(); fast++){
        if(nums[fast] != 0){
            nums[slow] = nums[fast];
            slow++;
        }
    }
    // slow之后全部填0
    for(int i = slow; i < nums.size(); i++){
        nums[i] = 0;
    }
}

复杂度分析

  • 时间复杂度:\(O(n)\),最多两次遍历数组
  • 空间复杂度:\(O(1)\),原地修改,仅两个指针变量

题目 2:复写零1089. 复写零 - 力扣(LeetCode)

题目描述

给你一个长度固定的整数数组 arr,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。要求:对输入的数组 就地 修改。

示例:

cpp 复制代码
输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:第一个0复写为两个0;第二个0复写两个0;最后的0后面超出数组长度,不扩展。

思路分析

如果从左往右直接复写:会覆盖掉还没遍历到的元素,导致数据丢失。 ✅ 正确方案:先统计有效长度,再从后往前双指针覆盖

  1. 先遍历一遍,统计:如果全部复写,实际会占用多少长度 len。遇到 0,长度 + 2,非 0+1。
  2. 两个指针:slow 指向复写完成后的最后一个位置,fast 指向原数组末尾。
  3. 从后向前遍历:
    • 如果 arr fast ≠ 0:直接把值复制到 arr slow,slow--,fast--
    • 如果 arr fast == 0:slow 位置写 0,slow--;再写一个 0,slow--;fast--
  4. 注意:slow 不能小于 0,保证不越界。

关键点:从后往前,避免还没读取的元素被提前覆盖。这是本题最容易踩坑的地方。

C++ 代码

cpp 复制代码
void duplicateZeros(vector<int>& arr) {
    int n = arr.size();
    int slow = 0;
    // 第一步:统计复写后占用的长度
    int fast;
    for(fast = 0; fast < n; fast++){
        if(arr[fast] == 0){
            slow += 2;
        }else{
            slow += 1;
        }
        if(slow >= n) break;
    }
    // 从后往前覆盖
    while(fast >= 0){
        if(arr[fast] != 0){
            arr[slow - 1] = arr[fast];
            slow--;
        }else{
            // 遇到0,复写两个0
            if(slow - 1 < n) arr[slow - 1] = 0;
            if(slow - 2 < n) arr[slow - 2] = 0;
            slow -= 2;
        }
        fast--;
    }
}

复杂度分析

  • 时间复杂度:\(O(n)\),两次遍历数组
  • 空间复杂度:\(O(1)\),原地修改

📌 两道题对比总结(博客重点)

表格

题目 指针方向 核心操作 坑点
移动零 从左向右 收集非 0,尾部补 0 不要一边遍历一边删除元素
复写零 先统计,从后向前 遇到 0 写两个 0 正向遍历会覆盖未读元素

共同点:快慢指针,原地数组操作,不开额外数组,属于同一系列双指针题型。

题型归类:数组原地元素搬迁 / 覆盖,笔试面试很喜欢放在一起考察,区分是否真正理解双指针而不是背模板。

小结

很多同学以为双指针只有快慢指针找环、对撞指针求和。这两道原地数组题,拓展了双指针的使用场景:元素搬迁。 移动零是基础入门版本;复写零是进阶版本,增加了 "正向覆盖污染数据" 的陷阱。掌握这两道,以后遇到原地数组类题目,第一反应就可以考虑快慢指针。

相关推荐
梦帮科技1 小时前
Linux 内存取证学与 Rootkit 猎杀——Volatility 核心剖析与内核隐藏模块逆向清除
linux·运维·服务器·神经网络·算法·网络安全
广州智造2 小时前
Radioss二次开发需要准备什么?
人工智能·算法·机器学习·cad·cae
多弗朗皮卡丘2 小时前
C++异常
开发语言·c++·异常
米糕闯编程2 小时前
鱼香ros2(十七)C++小海龟巡逻
开发语言·c++·机器人·ros2·服务·小海龟
zx_741484812 小时前
YOLOv3 核心原理详解:Darknet-53 架构、多尺度预测与 FPN 特征融合
算法·yolo·目标检测·yolov3
MoRanzhi12032 小时前
122.四平方和(暴力枚举)
python·数学·算法·蓝桥杯·完全平方数·暴力枚举·四平方和定理
倒头就睡的小比特2 小时前
贪心算法介绍
算法·贪心算法
栈帧之外3 小时前
【C++】红黑树实现
开发语言·c++
程序员老陆3 小时前
C++中同一个回调函数被多个设备并发调用:并发问题不在“函数”,而在“状态”
开发语言·c++·程序设计