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

🌈个人主页:秋风起,再归来~
🔥系列专栏: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. 完结散花

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

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

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

我们下期不见不散~~

​​​​

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决ImageIO无法读取部分JPEG格式图片问题

解决ImageIO无法读取部分JPEG格式图片问题 问题描述 我最近对在线聊天功能进行了一些内存优化&#xff0c;结果在回归测试时&#xff0c;突然发现有张图片总是发送失败。测试同事把问题转到我这儿来看&#xff0c;我仔细检查了一下&#xff0c;发现是上传文件的接口报错&#…

软件测试学习笔记丨Linux三剑客-grep

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32506 一、简介 1.1 grep命令 grep是一个全局查找正则表达式&#xff0c;并且打印结果行的命令。grep的输入是一个文件或者一个标准输入&#xff08;stdin&#xff09;&#xff0c;或者是一…

用JAVA写人工智能应用_JAVA_AI

目录 ​编辑 Java AI 介绍&#xff1a;Spring AI - Java领域的AI开发新利器 Spring AI 扩展&#xff1a;Spring AI Alibaba&#xff0c;简化Java应用AI集成 SpringBoot集成阿里云AI服务&#xff1a;构建对话应用指南 基于SpringBoot集成Spring AI Alibaba 1. 环境准备 2…

JavaScript将array数据下载到Excel中

具体代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widt…

【Windows命令】Windows下启动Nginx后,在任务管理器里面没有发现nginx.exe进程

如题&#xff0c;当在本地Windows环境下想用反向代理时&#xff0c;突然发现在任务管理器里面没有发现nginx.exe进程&#xff0c;但是端口又是占用的。这时就要用Windows命令了。 查询端口占用 netstat -ano | findstr :80 根据进程ID&#xff08;pid&#xff09;查询进程名称…

Java_EE(反射技术)

反射机制介绍: 什么是反射Java反射机制是Java语言一个很重要的特性&#xff0c;它使得Java具有了“动态性”。在Java程序运行时&#xff0c;对于任意的一个类&#xff0c;我们能不能知道这个类有哪些属性和方法呢&#xff1f;对于任意的一个对象&#xff0c;我们又能不能调用它…

IOS APP初体验-第2课:给Iphone App设置个ICON

目录 第一步、图片尺寸 第二步、找到项目内Assets节点&#xff0c;把自己的图片复制进来 第三步、图片设置 第四步、启动项目真机调试 第一步、图片尺寸 设置一张图片&#xff0c;要求图片格式JPG&#xff0c;图片尺寸1024px*1024px。 第二步、找到项目内Assets节点&#…

2024腾讯全球数字生态大会 | 线上直播活动参与教程

2024腾讯全球数字生态大会 | 线上直播活动参与教程 9月5-6日,2024腾讯全球数字生态大会,共见最新 全景式产品服务矩阵,了解智能科技如何成本优化、 生产提效、重塑商业生态、加速全球布局。 大会亮点 100大咖趋势洞察 100专业白皮书 100开发者活动福利 体验丰富前沿智能应用落…

SCALABLEANDEFFECTIVE IMPLICIT GRAPH NEURALNETWORKS ON LARGEGRAPHS

ICLR24 推荐指数&#xff1a; #paper/⭐⭐ 领域&#xff1a; 大图&#xff0c;图扩展 大概的工作&#xff1a;提出了针对子图的虚拟节点&#xff0c;让所有点都与其相连 相关工作&#xff1a; 传统GNN与Inplicit gnn 传统GNN的传播函数&#xff1a; Z ( l 1 ) ϕ ( W ( …

Linux常用功能整合

Linux Linux 前言一、常用操作以及概念 快捷键求助关机PATHsudo包管理工具发行版VIM 三个模式GNU开源协议 二、磁盘 磁盘接口磁盘的文件名 三、分区 分区表开机检测程序 四、文件系统 分区与文件系统组成文件读取磁盘碎片blockinode目录日志挂载目录配置 五、文件 文件属性文件…

01 Solidity--

第一个 Solidity 程序 Solidity 是一种用于编写以太坊虚拟机&#xff08;EVM&#xff09;智能合约的编程语言。 掌握 Solidity 是参与链上项目的必备技能 在 Remix 中&#xff0c;左侧菜单有三个按钮&#xff0c;分别对应文件&#xff08;编写代码&#xff09;、编译&#x…

Spring ApplicationContext初始化过程

Spring-01篇章 一、Spring 简介 Spring是一个开源的Java平台&#xff0c;它提供了全面的基础设施支持来帮助Java开发者更容易地开发Java应用程序。Spring框架的核心特点是依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;&#xff0c;这些使得开…

【H2O2|全栈】JS入门知识(一)

目录 JS入门 前言 准备工作 JS标签和文件 变量 数据类型 字符串 变量的交换 数据类型获取 数据类型转换 面试题 提问框和提示框 提问框 提示框 ​编辑​编辑控制台输出 ​编辑转义字符 结束语 JS入门 前言 本系列博客主要分享JavaScript的基础语法知识&…

RNA-seq全流程

第一部分&#xff1a;脚本的初始设置与参数解析 #!/bin/bash# 检查输入参数 if [ "$#" -lt 1 ]; thenecho "Usage: $0 -f <sample_file> -d <data_directory> -s <script_directory> -g <group_file>"exit 1 fi# 使用 R 语言的 a…

2025推荐选题|基于Springboot和vue的智慧阅读管理系统

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

Java - WebSocket

一、WebSocket 1.1、WebSocket概念 WebSocket是一种协议&#xff0c;用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接&#xff0c;这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发&#xff0c;并于2…

【CSS】houdini自定义CSS属性实现渐变色旋转动画

现有一段代码&#xff0c;在不旋转整个元素的前提下&#xff0c;渐变背景无法应用动画 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initia…

专业模拟训练头显,Varjo XR-4 如何开启虚拟仿真新模拟时代

虚拟仿真模拟训练是提升技能熟练度与工作安全性的有效解决方案&#xff0c;Varjo XR-4作为专业模拟训练头显&#xff0c;凭借其出色的技术特性和性能&#xff0c;正在引领虚拟仿真模拟训练进入一个全新的时代。 一、卓越的视觉体验 高分辨率显示器&#xff1a;Varjo XR-4配备…

计算机毕业设计 基于Python的美术馆预约系统的设计与实现 Python毕业设计 Python毕业设计选题【附源码+安装调试】

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

三子棋(C 语言)

目录 一、游戏设计的整体思路二、各个步骤的代码实现1. 菜单及循环选择的实现2. 棋盘的初始化和显示3. 轮流下棋及结果判断实现4. 结果判断实现 三、所有代码四、总结 一、游戏设计的整体思路 &#xff08;1&#xff09;提供一个菜单让玩家选择人机对战、玩家对战或者退出游戏…