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后的长度已经达到/超出原长度,即可结束循环(大家可以接着往下看,超出的情况我会讲到):

arrslow为0,fast前进两步:

本次循环结束,slow++:

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

这次循环arrslow依旧非0,操作同上:

这次arrslow是0,fast+=2,然后slow++:

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

arrslow为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. 将数组最后一个位置(arr7)设为 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(Numsslow不为0的情况都这样处理),因为fast的位置,对应的就是slow元素复写后的位置(不理解的同学可以自己写出来这个例子复写0后的结果对照着想一下):

指针前移:

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

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

同上操作:

Numsslow==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--;
            }
        }
    }
};

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

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

相关推荐
先吃饱再说4 小时前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰7 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术8 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六11 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术12 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize13 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode