【优选算法】——双指针(上篇)!

news2024/10/18 23:36:54

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++刷题算法总结
🔖克心守己,律己则安

目录

前言:双指针

1. 移动零(easy)

2. 复写零(easy)

3. 快乐数(medium)

4. 盛⽔最多的容器(medium)

5. 完结散花


前言:双指针

常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。

对撞指针:⼀般⽤于顺序结构中,也称左右指针。

• 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼 近。

• 对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循 环),也就是:

         ◦ left == right (两个指针指向同⼀个位置)

        ◦ left > right (两个指针错开)

快慢指针:⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列 结构上移动。

这种⽅法对于处理环形链表或数组⾮常有⽤。

其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快 慢指针的思想。 快慢指针的实现⽅式有很多种,

最常⽤的⼀种就是:

• 在⼀次循环中,每次让慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢。

1. 移动零(easy)

「数组分两块」是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部 分。这种类型的题,⼀般就是使⽤「双指针」来解决。 

题目链接icon-default.png?t=O83Ahttps://leetcode.cn/problems/move-zeroes/

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

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:

输入: nums = [0]
输出: [0]
 

进阶:你能尽量减少完成的操作次数吗?

 解题思路:

1、我们先定义两个指针cur和pre。

2、我们用cur来扫描整个数组。

        a、如果cur位置为0,我们不进行处理,cur++。

        b、如果cur位置不为0,我们让cur位置和pre位置的元素交换,pre++,cur++。

3、通过以上的操作,我们把该数组进行了区域划分:[0,pre)区域的元素是非零的,[pre,cur)区域的元素是0,而[cur,len)区域是没有判断的区域。

4、循环以上的操作,当cur走到数组末尾时,结束循环。

具象图

抽象图 

解题代码: 

class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        int len=nums.size();
        for(int cur=0,pre=0;cur<len;cur++)
        {
            if(nums[cur]!=0) swap(nums[cur],nums[pre++]);
        }
    }
};

2. 复写零(easy)

题目链接icon-default.png?t=O83Ahttps://leetcode.cn/problems/duplicate-zeros/description/

给你一个长度固定的整数数组 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、我们先定义两个指针cur=-1,des=1。

2、cur扫描数组:

        a、cur位置为0,des走两步

        b、cur位置不为0,des走一步

3、当des大于等于len-1位置时结束循环

4、cur的作用是走到最终被重写的元素,而des的作用是找到我们从后往前复写开始的位置

5、如果我们从前往后复写的话,有可能会把我们需要复写的元素覆盖!
 

        int cur = -1;
        int des = -1;
        int len = arr.size();
        while (des < len-1)
        {
            cur++;
            if (arr[cur] == 0)
                des += 2;
            else
                des++;
        }

6、处理边界情况,如果最后一个要复写的元素是0,那么des的位置会走到len(即数组的长度不够我们复写两个0),这时我们要单独处理这种情况,提前复写一个0即可。 

        if (des == len)
        {
            cur--;
            arr[des - 1] = 0;
            des -= 2;
        }

7、从前往后复写

        while (cur >= 0)
        {
            if (arr[cur] == 0)
            {
                arr[des--] = 0;
                arr[des--] = 0;
            }
            else  arr[des--] = arr[cur];
            cur--;
        }

解题代码:

class Solution
{
public:
    void duplicateZeros(vector<int>& arr)
    {
        int cur = -1;
        int des = -1;
        int len = arr.size();
        //找到最后一个数
        while (des < len-1)
        {
            cur++;
            if (arr[cur] == 0)
                des += 2;
            else
                des++;
        }
        //处理边界情况
        if (des == len)
        {
            cur--;
            arr[des - 1] = 0;
            des -= 2;
        }
        //从后往前复写
        while (cur >= 0)
        {
            if (arr[cur] == 0)
            {
                arr[des--] = 0;
                arr[des--] = 0;
            }
            else  arr[des--] = arr[cur];
            cur--;
        }
    }
};

3. 快乐数(medium)

题目链接icon-default.png?t=O83Ahttps://leetcode.cn/problems/happy-number/description/

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:

输入:n = 2
输出:false

解题思路:

 

1、通过分析以上两个例子,我们会发现当数字为快乐数时,它其实是在1当中死循环,当数字不是快乐数时,它其实是以自己初识位置为起点,但不能变化为一,最后进入一个死循环的过程。

2、而我们只要找到这个过程当中刚进入循环的数字是否为1即可判断该数字是否是快乐数。

3、我们可以快速的联想到链表当中的一个经典题型,这题也是如此,我们只要用快慢指针的思想就可以解决这个问题!

思考:为什么数字的变化一定只有这两种情况呢?

1、变为1后进入死循环。

2、最后没有变为1进入死循环。

还有一种情况:一直变化,不进入循环。(可能吗?)

题目说明了n的最大值是2^31-1即2,147,483,647。取最大值的每一位的平方和为260,所以n变化所得结果的范围在1~260之间。假设n从开始非常不幸经过了260次变化,恰好1~260之间的数字都变化得到过,那在261次变化时,其结果一定是1~260之间的某个元素,所以这个过程中一定会进入循环!(鸽巢原理)

4. 盛⽔最多的容器(medium)

题目链接icon-default.png?t=O83Ahttps://leetcode.cn/problems/container-with-most-water/description/

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:

输入:height = [1,1]
输出:1

解题思路:

1、暴力枚举:这种题目我们最先想到的就是暴力枚举所有的面积,然后不断更新结果,找到最大的面积即可。不过这种n^2的算法一般在leetcode中等题型上提交都会超出时间的限制。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int len=height.size();
        int ret=0;
        for(int left=0;left<len-1;left++)
        {
            for(int right=1;right<len;right++)
            {
                int tmp=(right-left)*min(height[left],height[right]);
                ret=max(tmp,ret);
            }
        }
        return ret;
    }
};

 2、不过尽管如此,暴力解决问题的算法还是有其意义的,我们一般很难直接想到最优解法,所以我们可以在暴力解法的基础上去不断进行优化!

更优解法:

3、我可以定义left在数组的开头,right在数组的结尾。记录此时的面积,然后比较这两个位置的数值大小。

        a.我们先固定数值较小(假设是left)的位置,然后移动数值较大(right)的一侧。这时我们会发现移动数值较大一侧元素时,枚举的所有情况的面积都是减小的!原因在于:首先宽是不断减小的,如果高right减小,总高度减小,如果增大,高度不变。这也就意味着固定left(较小一侧)位置所枚举的最大值就是当前位置,此时right就不再需要左移去,枚举其他情况(没有意义)!而此时固定较小端的所有情况就相当于枚举完了。

        b.接着我们就让较小端的一侧++或--,循环知道left==right。此时所有情况都以枚举完成,我们只要将最大的结果记录下来就完了。

解题代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int len=height.size();
        int left=0,right=len-1,ret=0;
        while(left<right)
        {
            //更新结果
            int tmp=(right-left)*min(height[left],height[right]);
            ret=max(ret,tmp);
            //
            if(height[left]<height[right]) left++;
            else right--;
        }
        return ret;
    }
};

5. 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2218090.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode C/C++跳转到定义、自动补全、悬停提示突然失效

昨天像往常一样用vscode连接云服务器写代码&#xff0c;突然发现跳转到定义、自动补全、悬停提示功能全部不能正常使用了&#xff0c;今天折腾了一上午&#xff0c;看了一大堆教程&#xff0c;最后可算是解决了&#xff0c;因为大家说不定会遇到和我一样的问题&#xff0c;所以…

【工具篇】MLU运行XInference部署手册

文章目录 前言一、平台环境准备二、代码下载三、安装部署1.正常pip 安装 四、运行结果展示1.如果界面404或没有东西请这样做2.运行效果 前言 Xorbits Inference&#xff08;Xinference&#xff09;是一个功能强大、用途广泛的库&#xff0c;旨在为语言、语音识别和多模态模型提…

自监督学习:引领机器学习的新革命

引言 自监督学习&#xff08;Self-Supervised Learning&#xff09;近年来在机器学习领域取得了显著进展&#xff0c;成为人工智能研究的热门话题。不同于传统的监督学习和无监督学习&#xff0c;自监督学习通过利用未标注数据生成标签&#xff0c;从而大幅降低对人工标注数据…

数据库-01MYSQL-001MySQL知识点查漏补缺

MySQL知识点查漏补缺 数据库常识不常见知识点&#xff1a; 数据库常识 知识点001&#xff1a; between…and … 包含临界值。 知识点002&#xff1a;任何内容与null相加等于null。 知识点003&#xff1a;模糊查询涉及的函数有&#xff1a;like&#xff0c;between…and…, in/…

机器的“眼睛“:计算机视觉技术背后的魔法

计算机视觉&#xff0c;作为人工智能领域中的一颗璀璨明珠&#xff0c;正逐步改变着我们的生活方式。它赋予了机器“看”的能力&#xff0c;使得计算机能够从图像和视频中提取信息并进行分析&#xff0c;就像人类用眼睛和大脑来理解世界一样。本文将带你走进计算机视觉的世界&a…

解决linux服务器磁盘占满问题(详细,有效,100%解决)

应用场景&#xff1a; 在我们的日常开发中&#xff0c;我们的服务器总是在不知不觉中磁盘莫名奇妙少了很多空间&#xff0c;或者被占满了&#xff0c;如果这时候要想要存储什么文件&#xff0c;突然发现空间不够了。但我们通常也不知道那些文件占用的空间大&#xff0c;这时候…

ANSYS Workbench纤维混凝土3D

在ANSYS Workbench建立三维纤维混凝土模型可采用CAD随机几何3D插件建模后导入&#xff0c;模型包含球体粗骨料、圆柱体长纤维、水泥砂浆基体等不同组分。 在CAD随机几何3D插件内设置模型参数后运行&#xff0c;即可在AutoCAD内建立三维纤维混凝土模型&#xff0c;插件支持任意…

牛客习题—线性DP 【mari和shiny】C++

你好&#xff0c;欢迎阅读我的文章~ 个人主页&#xff1a;Mike 所属专栏&#xff1a;动态规划 mari和shiny mari和shiny ​ 分析: 使用动态规划的思路来解决。 思路&#xff1a; 分别统计s&#xff0c;sh&#xff0c;shy的数量即可。使用ss来统计字符s的数量&#xff0c;使…

LC1523.在区间范围内统计奇数数目

一开始没审题&#xff0c;居然构造了一个数组去做… 然后重新看&#xff0c;首先先想到的暴力解就是遍历low到high&#xff0c;然后每一个数都对二取余。但是这样的暴力解就没什么锻炼 那肯定再想一个思路&#xff0c;Low和high都有两种情况&#xff0c;要么是奇数&#xff0c…

30.第二阶段x86游戏实战2-遍历周围-C++遍历二叉树(玩家角色基址)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

衡石分析平台系统分析人员手册-应用查看

应用查看​ 应用创作界面展示了用户可以查看的所有应用。 用户可以使用平铺视图或列表视图查看应用。同时支持通过搜索、过滤、排序等方式快速查找应用。 应用视图​ 应用创作支持平铺视图和列表视图两种展示方式&#xff0c;默认以平铺视图的方式展示应用&#xff0c;用户可…

2024 蚂蚁SEO蜘蛛池对网站收录的帮助

《2024 蜘蛛池对网站收录还有效果吗&#xff1f;》 在网站优化的领域中&#xff0c;蜘蛛池曾经是一个备受关注的工具。然而&#xff0c;随着搜索引擎算法的不断演进&#xff0c;人们对于 2024 年蜘蛛池对网站收录是否还有效果产生了疑问。 一、什么是蜘蛛池&#xff1f; 蜘蛛池…

APQP在制造行业的应用:搭上数字化项目管理平台很nice

APQP&#xff08;Advanced Product Quality Planning&#xff0c;即产品质量先期策划&#xff09;最早由汽车行业引入&#xff0c;并因其在质量管理方面的显著效果而逐渐被其他制造业领域所采纳。 APQP提供了一种从产品设计的最初阶段到生产过程的全面质量管理框架&#xff0c;…

使用fpm工具制作Vim.rpm包

背景&#xff1a;生产环境中的CentOS 7在安全扫描中被扫描出vim存在堆缓冲区溢出&#xff08;CVE-2024-45306&#xff09;等漏洞。根据漏洞说明&#xff0c;需要升级到最新版。 奈何CentOS 7已经停止维护了&#xff0c;所以&#xff0c;想在网上找一个最新版的vim.rpm相当不容易…

数字图像处理:图像复原应用

数字图像处理&#xff1a;图像复原应用 1.1 什么是图像复原&#xff1f; 图像复原是图像处理中的一个重要领域&#xff0c;旨在从退化&#xff08;例如噪声、模糊等&#xff09;图像中恢复出尽可能接近原始图像的结果。图像复原与图像增强不同&#xff0c;复原更多地依赖于图…

ES6 Promise的用法

学习链接&#xff1a;ES6 Promise的用法&#xff0c;ES7 async/await异步处理同步化&#xff0c;异步处理进化史_哔哩哔哩_bilibili 一、同步与异步区别 1.JavaScript代码是单线程的程序&#xff0c;即通过一行一行代码顺序执行&#xff0c;即同步概念。 2.若处理一些简短、…

数据结构部分混淆

1.随机存储和顺序存储&#xff1a; 随机存取&#xff1a;数组&#xff0c;当存储器中的数据被读取或写入时&#xff0c;所需要的时间与该数据所在的物理地址无关 顺序存取&#xff1a;链表&#xff0c;当存储器中的数据被读取或写入时&#xff0c;所需要的时间与该数据所在的物…

力扣之1412.查找成绩处于中游的学生

题目&#xff1a; sql建表语句&#xff1a; Create table If Not Exists Student (student_id int, student_name varchar(30)); Create table If Not Exists Exam (exam_id int, student_id int, score int); Truncate table Student; insert into Student (student_id,…

linux环境下的程序设计与git操作

目录 前言&#xff1a; 进度条小程序&#xff1a; 先介绍几个背景知识 代码实现 Git操作 总结 其他指令 前言&#xff1a; 本文将重点介绍1. linux下的程序设计&#xff0c;并使用linux下的几个函数接口。实现一个简单的小程序 2.本着开源精神&#xff0c;进行git操作。…

OPENSSL-2023/11/10学习记录-C/C++对称分组加密DES

对称分组加密常用算法&#xff1a; DES 3DES AES 国密SM4 对称分组加密应用场景&#xff1a; 文件或者视频加密 加密比特币私钥 消息或者配置项加密 SSL通信加密 对称分组加密 使用异或实现一个简易的对称加密算法 A明文 B秘钥 AB密文AB (AB)B A 密码补全和初始化 数…