• 【算法学习】-【双指针】-【复写零】


    LeetCode原题链接:1089. 复写零

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

    注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

    • 示例 1:
      输入:arr = [1,0,2,3,0,4,5,0]
      输出:[1,0,0,2,3,0,0,4]
      解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]
    • 示例 2:
      输入:arr = [1,2,3]
      输出:[1,2,3]
      解释:调用函数后,输入的数组将被修改为:[1,2,3]

    通过这道题可以获得的经验主要有如下两点:

    • 实现“复写”之类的操作时,可以优先考虑从后往前进行复写,即多思考算法执行的顺序
    • 题目要求在原地对数组进行修改,但在分析时可以先按另外开辟空间的角度进行分析,然后再根据过程中进行操作的特点,通过指针模拟出在原数组中模拟出整个过程

    下面是解题思路以及具体代码:

    1、BF解法
    根据题目描述,很容易想到这个暴力解法,也不涉及到有关双指针算法的运用,所以这里仅进行简单的陈述,对于想了解双指针解法的朋友可直接跳过~。
    思路:从后往前遍历,每遇到一个0时,就从数组的倒数第二位开始,往自己的后一位去复写,即arr[n] = arr[n-1];直至到当前0的后一个位置。
    如示例1中,从后往前第一个0的下标为4,那么当从后往前遍历到下标为4时,就从下标为6的位置(倒数第二位)开始依次往自己的后一位复写,直到下标为5(到当前0的后一个位置)。循环直至遍历完整个数组

    具体代码为:

    class Solution {
    public:
        void duplicateZeros(vector<int>& arr) 
        {
            int end = arr.size() - 1;
            int mov = end - 1;
            while(mov >= 0)
            {
                if(arr[mov] == 0)
                {
                    while(end > mov)
                    {
                        arr[end] = arr[end - 1];
                        end--;
                    }
                }
                mov--;
                end = arr.size() - 1;
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2、双指针模拟容器
    这是相对于解法1来说更好的解法,也是本文说明的重点。(PS:主要是对官方题解的理解总结
    我们可以先进行另开空间的过程分析,最后再通过指针在原数组上模拟。
    那么从本题的要求来看,可以通过一个新的数组来进行数据的存储,即遍历原数组,遇到非0元素就将其放入一次进新数组中,遇到0就将其放入两次进vector中,直到新数组的大小等于或超过了(伏笔)原数组;
    用示例1进行如上过程就为:
    在这里插入图片描述

    最后再将新数组对应原数组中的位置进行复写即可。
    那么接下来的问题就是如何在原数组中模拟出这个过程。

    • 首先是 “构建” 新数组
      说是构建,其实本质上是在原数组中找到新数组中最后一个数(或者说是用于复写原数组的第一个数),为复写做准备工作。
      那么结合过程,我们可以这样找到这个数:
      假设一个变量为mov用于表示将要放入新数组中的下一个数在原数组中的下标(也就是上图中的i),另一个变量size用于控制放入操作是否需要停止。接着遍历原数组,遇到非零元素movsize一起++;遇到零元素mov++size+=2表示将0放了两次到新数组中;循环直至size大于等于原数组的大小。循环结束后mov的位置即为新数组的最后一个数(示例一中的4)在原数组中的位置的下一个位置,故让mov--指向新数组的最后一个数在原数组中的位置,准备进行复写

    • 接下来是复写过程
      原数组最后一个元素开始进行复写;用一个指针end指向它,接着以mov指针所对应的元素为判断依据,若是非零元素,则执行一次复写,复写完成后movend往前移一位;若是零元素,则执行两次复写,复写完成后mov往前移一位end往前移两位。循环执行如上过程直至复写完整个数组。

    • 还有个坑:
      上面说过,当新数组的大小等于或超过了原数组的大小时停止复写,等于原数组算是“正常”情况,新数组中零元素的个数为偶数个,即在原数组上复写时可以执行正确执行两次复写;但还有新数组的大小超过原数组的大小的情况,此时新数组中零元素的个数并不是偶数个,按照正常复写过程直接在原数组上复写会覆盖掉之前的数据,出现这种情况原因是最后需要复写两个零但空间只能再容纳一个零了,如这个测试用例:
      [8,4,5,0,0,0,0,7]
      i = 5时,新数组中的size = 7,此时仅能再放入一个0,这意味着用双指针进行复写的时候,最后一个0只能复写一次
      解决方法
      根据遇到0放入两次的操作,不符合条件跳出循环后,size的值是大于原数组的大小的(准确来说等于原数组大小+1),对这种情况进行特殊判断后先将最后一个元素进行复写,且movend都往前移一位后,再进行正常的复写即可。

    完整的代码如下

    class Solution {
    public:
        void duplicateZeros(vector<int>& arr) 
        {
            int mov = 0;
            int size = 0;
            while(size < arr.size())
            {
                if(arr[mov] == 0)
                {
                    size+=2;
                }
                else
                {
                    size++;
                }
                mov++;
            }
    
            mov--;
            int end;
            if(size == arr.size() + 1)	//特殊判断
            {
                end = size - 2;
                arr[end] = 0;
                mov--;
                end--;
            }
            else
            {
                end = size - 1;
            }
            
            while(mov>= 0) 
            {
                arr[end] = arr[mov];	//正常复写一次
                end--;
                if(arr[mov] == 0)		//等于零再复写一次
                {
                    arr[end] = arr[mov];
                    end--; 
                } 
                mov--;
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

  • 相关阅读:
    前端 Array.sort() 源码学习
    没看过源码,却能找到Seata源码中的BUG
    期末人福音——用Python写个自动批改作业系统
    使用Spring WebFlux和Spring Cloud的反应式微服务
    pytorch autograd 自动微分
    软件测试工程师涨薪攻略!3年如何达到30K!
    七天接手react项目 系列 —— react 起步
    Callble接口创建多线程【详细总结】
    elementUI 源码-打造自己的组件库,系列四:Dialog组件
    C# Socket通信从入门到精通(10)——如何检测两台电脑之间的网络是否通畅
  • 原文地址:https://blog.csdn.net/qq_62696027/article/details/133485883