LeetCode1089. 复写零(双指针精讲)

题目链接

1089. 复写零 - 力扣(LeetCode)

题目理解

本题不难理解,其实就是将数组中的0复写一遍,数组空间不能变长,因此,我们每复写一个0,数组末尾的数字就会被覆盖掉。

输入: arr = [1,0,2,3,0,4,5,8]

输出: [1,0,0,2,3,0,0,4]

解释:将数组中的0复写,复写了两个0,因此数组的空间被占了两个,数字5和数字8被覆盖。

本题的难点在于,题目要求只能在原本的数组上修改,且数组不能够超过原来的长度。那我们想insert将0插入,然后删除超过长度的部分的思路就不能使用了。

解题思路

核心思路:双指针 + 从后向前填充

这道题的关键在于:我们不能从前向后直接复制,因为这样会覆盖还未处理的元素。例如,如果我们在位置 i 遇到 0,直接将它复制到位置 i+1,那么原位置 i+1 的元素就会被覆盖,而这个元素可能本身也需要被处理。

思路分析
  1. 问题转化:我们需要在数组中找到每个 0 后都插入一个 0,同时保持数组长度不变。这会导致一些元素被"挤出"数组。

  2. 关键问题:原数组中的哪些元素最终会留在新数组中?

  3. 双指针方法

    • 我们使用两个指针:slow(慢指针)遍历原数组,fast(快指针)模拟在新数组中的位置

    • slow 每遍历一个元素,fast 就前进 1 步(如果元素是 0,则前进 2 步)

    • fast 到达或超过数组末尾时,slow 指向的就是原数组中最后一个会被保留在新数组中的元素

老规矩,咱们直接上图例,主播手把手带大家过一遍。

第一步:找到 slow 的位置(确定哪些元素会被保留)

先设置两个指针,slow和fast,初始值全部为0,指向首元素,循环结束条件呢,是fast>=arr.size(),因为此时代表复写0后的长度已经达到/超出原长度,即可结束循环(大家可以接着往下看,超出的情况我会讲到):

arr[slow]为0,fast前进两步:

本次循环结束,slow++:

下一次循环,arr[slow]非0,fast前进一步,循环结束,slow++:

这次循环arr[slow]依旧非0,操作同上:

这次arr[slow]是0,fast+=2,然后slow++:

非0,都加一(注意此时fast值为7,因为数组从0开始,因此并未结束循环):

arr[slow]为0,fast+=2,此时fast为9>arr.size(),为超出情况。为什么会超出呢?因为最后加等了2,也就是最后一个数字为0

此时:

  • slow = 5(指向原数组中的第一个 0)

  • fast = 9

分析fast = 9 表示如果完全复制所有 0,新数组需要 9 个位置,但我们的数组只有 8 个位置。因此最后一个 0 只能被复制一次(而不是两次)。

第二步:处理边界情况

因为 fast = 9 > n = 8(n为数组长度),我们需要特殊处理:

  1. 将数组最后一个位置(arr[7])设为 0

  2. slow 回退到 4(相当于是从倒数第二次的位置开始)

  3. fast 设为 n-2(n为数组长度,从倒数第二个位置开始填充)

此时

  • slow = 4(指向元素 1)

  • fast = 6

  • 数组变为:[0, 5, 2, 0, 1, 0, 2, 0](最后一个位置已经处理)

解释:说人话就是,我们知道这种情况最后是一个0(第二个复写的0会越界),因此直接在最末尾放上一个0,这一位数组就处理好了,我们将指针回溯到前一步的位置。
如果最后一个数字不为0,直接让fast=n-1即可(n为数组长度)

第三步:从后向前填充数组

该例子为特殊情况(fast>n),我们按第二步操作后,指针指向情况如下:

现在将slow的值给fast(Nums[slow]不为0的情况都这样处理),因为fast的位置,对应的就是slow元素复写后的位置(不理解的同学可以自己写出来这个例子复写0后的结果对照着想一下):

指针前移:

此时Nums[slow]==0,那么我们将fast以及fast前面一位的位置写为0(复写了两次,因此fast--两次,循环结束时,slow--一次):

不为0,直接将slow的值给fast,然后指针前移:

同上操作:

Nums[slow]==0,fast以及fast前面一位的位置写为0(复写了两次,因此fast--两次,循环结束时,slow--一次):

完成!下面我们看一下源码,主播给大家详细注释过了:

详解源码

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int n = arr.size();
        
        // 第一阶段:定位最后一个需要保留的元素
        int slow = 0;  // 遍历原数组
        int fast = 0;  // 在新数组中的位置
        
        while (fast < n) 
        {
            if (arr[slow] == 0) 
            {
                fast += 2;  // 遇到0,新数组中需要占两个位置
            } 
            else 
            {
                fast += 1;  // 非0元素,只占一个位置
            }
            
            // 当新数组位置已满时,停止遍历
            if (fast >= n) 
            {
                break;
            }
            
            slow++;  // 继续检查下一个元素
        }
        
        // 第二阶段:处理边界情况
        // 如果最后一个元素是0会导致数组越界,特殊处理
        if (fast > n) 
        {
            arr[n - 1] = 0;  // 最后一个位置设为0
            slow--;          // 回退到前一个元素
            fast = n - 2;    // 从倒数第二个位置开始
        } 
        else 
        {
            fast = n - 1;    // 正常情况,从最后一个位置开始
        }
        
        // 第三阶段:从后向前完成复写操作
        while (slow >= 0) 
        {
            if (arr[slow] != 0) 
            {
                // 非0元素,直接复制
                arr[fast--] = arr[slow--];
            } 
            else 
            {
                // 0元素,需要复制两次
                arr[fast--] = 0;
                if (fast >= 0) // 确保不会数组越界
                {  
                    arr[fast--] = 0;
                }
                slow--;
            }
        }
    }
};

学会了就给主播点个赞呗?(✪ω✪)

---------(如有问题,欢迎评论区提问)---------

相关推荐
im_AMBER2 小时前
Leetcode 91 子序列首尾元素的最大乘积
数据结构·笔记·学习·算法·leetcode
Tisfy2 小时前
LeetCode 840.矩阵中的幻方:模拟(+小小位运算)
算法·leetcode·矩阵
Swift社区2 小时前
LeetCode 461 - 汉明距离
算法·leetcode·职场和发展
CoderCodingNo2 小时前
【CSP】CSP-XL 2025辽宁复赛真题-第三题, 小L打比赛(match)
数据结构·算法
_OP_CHEN2 小时前
【算法基础篇】(四十)数论之算术基本定理深度剖析:从唯一分解到阶乘分解
c++·算法·蓝桥杯·数论·质因数分解·acm/icpc·算数基本定理
YGGP10 小时前
【Golang】LeetCode 64. 最小路径和
算法·leetcode
古城小栈12 小时前
Rust变量设计核心:默认不可变与mut显式可变的深层逻辑
算法·rust
电商API&Tina12 小时前
跨境电商 API 对接指南:亚马逊 + 速卖通接口调用全流程
大数据·服务器·数据库·python·算法·json·图搜索算法
LYFlied12 小时前
【每日算法】LeetCode 1143. 最长公共子序列
前端·算法·leetcode·职场和发展·动态规划