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

题目 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 正向遍历会覆盖未读元素

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

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

小结

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

相关推荐
apcipot_rain2 小时前
C++转Java日记(02)
java·开发语言·c++
杜 硕2 小时前
从零开始的C++生活——第七讲——02
开发语言·c++·生活
Rabitebla10 小时前
【Linux 底层原理】进程(十):进程等待详解---从僵尸进程到 wait、waitpid 与 status
linux·c++·笔记·学习·visual studio
西西弗Sisyphus12 小时前
C++ 超时互斥锁(Timed Mutex):std::timed_mutex 与 std::unique_lock
c++
其实防守也摸鱼14 小时前
个人学习 Pwn 思路:从基础到漏洞利用的进阶路径
大数据·前端·人工智能·chrome·算法·安全·网络安全
今天要早睡_14 小时前
C++01_入门基础复习
开发语言·c++
Nuanyt15 小时前
LeetCode Hot 100 链表专题(五)· 2 题(Java)
java·算法·leetcode·链表
Fuyo_111917 小时前
C++ 运行时多态与虚函数机制全面剖析【面试重点】
开发语言·c++·面试
w_zero_one18 小时前
递归算法(一)
java·开发语言·算法