LeetCode Hot100实战之双指针

news2024/11/17 7:39:00

公众号:自学编程村回复“书籍”,领取1000多本计算机书籍。涵盖语言语法、数据结构算法、AI相关书籍、开发相关等等各种书籍,应有尽有,你总会用到。

回复“C语言”,领取原创C语言九讲PDF教程(数万字、百图)。

回复学习路线,领取C++和Java学习路线和资源推荐总总结。

这篇文章终于来了哈哈,为大家带来LeetCode Hot100中双指针内容我的总结。

目录

LeetCode 238 移动零

LeetCode 11 盛水最多的容器

LeetCode 15 三数之和

LeetCode 42 接雨水


我们本节为大家带来双指针内容的总结。

 看完本篇文章呢,你将会了解双指针的基本使用方法,会初步体会到双指针 是如何使用的,以及我们在平时做题中,在什么样的场景下应该去想到用双指针。

看完本篇文章,你将收获一道easy,两道mid和一道hard。并且对于hard接雨水的题目,我们会给出两种算法,并详细解答为什么(官方题解似乎只把过程阐述清楚了,而对于原因阐述较少)。

其实双指针思想之深邃,远不止此,大家可以不用拘泥于Hot100上的这几道题,可以再LeetCode的专题中再去找一些别的题来更深一层次地体会双指针的使用之处。

我们先来说说双指针的模板是怎么样的?所谓模板就是板子,一般情况下的算法步骤。

void two_pionter()
{
    for(int i = 0; i  < n; i++)
    {
        int j = i;
        while(judge( )) j++;
    } 
}

注意,上面的代码只是一种思想,实际上的代码并不一定会和其完全一样。

我认为,它实际上和贪心、DP等是一样子的,只存在思想,而没有说有非常通用的模板。我们需要在平常的练习中多多地总结和思考。

LeetCode 238 移动零

我们先来说238题,移动零。

283. 移动零 - 力扣(LeetCode)

这道题实际上很简单。但是乍一看好像有的同学也会被男主hhh。

这道题要想明白一个点,就会比较容易了。那就是:移动到数组后面的数,都是0。并不是其他不同的数。也就是说,移动到数组后面的数都是相同的!既然是相同的,那就好办了。

所以,我们就可以:把前面所有的数都移动到前面去之后,把后面的数手动设置成0,就好了。不需要考虑0被我们覆盖掉的问题,因为我们知道,我们覆盖掉的都是0。最后移动完之后,手动把数组后面所有的元素都变成0就可以了。

那这样以后,代码就比较简单了。下面是一个我自己写的示例代码:

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

这道题也是双指针一道比较简单的应用。

LeetCode 11 盛水最多的容器

我们接下来来看第二题。

11. 盛最多水的容器 - 力扣(LeetCode)

第二题,需要想明白一个点。想明白这个点之后,问题就会变得简单。

需要想明白什么呢?实际上是一种贪心的思路。或者说,它是完全搜索(就是两次O(N^2)搜索的剪枝)

就是拿两个双指针放在两端,分别往中间走,每次走的是双指针指向的两个柱子中,矮的那个柱子。这样就一定能找得到最大的盛水容器。

那为什么是这样呢?

首先,根据局部贪心,我们肯定优先走短的,而不是长的。那为什么走短的,最后就一定可以找到最大的而不会被漏掉呢?

因为一旦被遍历到,那肯定就能找到最大的。因为我们是在所有的容器中找最大值。那问题就是最大值有没有可能没有被遍历到呢?

我们来假设双指针指向的两个柱子分别是左边的和右边的柱子。并且此时,左边的柱子是高的,右边的柱子是矮的。我们分别用红色的矩阵来表示,并在图上写上了高和矮。

那么,我们是要移动矮的柱子,所以矮的柱子的右边情况是都可以考虑到的,我们现在要考虑的是被剪枝优化掉的情况,也就是左边高的的柱子的右侧的三种情况。

我们分别用1,2,3来去表示三种情况。

1、比左边的柱子矮,但是呢比右边的红色柱子高。

2、比左边的柱子矮,并且比右边的柱子矮。

3、比左边的柱子高。

这三种情况的分析如图所示。

所以说,这三种情况我们是都不用考虑的。都不会比当前的容器的值要大。所以我们不用移动高的柱子,只要移动矮的柱子就可以了。

想明白这个点,这个问题就比较简单了。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int i = 0, j = height.size() - 1;
        int ans = 0;
        while(i < j){
            ans = max(ans, min(height[i], height[j]) * (j - i));
            if(height[i] > height[j]) j--;
            else i++;
        }
        return ans;
    }
};

LeetCode 15 三数之和

继续来看第三题。第三题比较简单。

15. 三数之和 - 力扣(LeetCode)

为什么说比较简单呢?

因为要求三数之和为0,和求两数之和差不多。因为要求三数之和,我们只要枚举第一个数,然后让第二个数和第三个数的和等于第一个数的负数就可以了。后面的操作(即让第二个数和第三个数的和等于第一个数的负数)我们可以用双指针来去做。

这道题很简单。但是要注意数字可能会重复,所以要去重。

下面是代码:

class Solution {
public: 
    vector<vector<int>> ans;
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());

        for(int i = 0; i < nums.size(); i++)
        {
            if(i && nums[i] == nums[i-1]) continue;
            int k = nums.size() - 1;
            //即使用双指针 j, k 来去寻找 nums[j] + nums[k] = -nums[i]的,不可以包含重复的,且无序
            for(int j = i + 1; j < nums.size(); j++)
            {
                if(j != i + 1 && nums[j] == nums[j - 1]) continue;
                while(j < k && nums[j] + nums[k] > -1 * nums[i])
                {
                    k--;
                }
                if(nums[j] + nums[k] == -1 * nums[i]) {
                    if(j == i ||  j == k || i == k) continue;
                    ans.push_back({nums[i], nums[j], nums[k]});
                }
            }
        }
        return ans;
    }
};

好,我们最后来看接雨水。

LeetCode 42 接雨水

42. 接雨水 - 力扣(LeetCode)

这道题我们会说两种做法(虽然后一种是前一种的优化,但也是后一种思想的基础),并且会详细解答为什么。

接雨水这道题,和双指针有关系,但是也没有那么大的关系。我觉得它更加类似于一个伪单调栈的问题。

这道题首先要搞明白雨水是怎么样被接的。如果你能把它描述出来,那么也基本上就可以做出来这道题了。

这里我的描述方式是:对于每一个点的位置,取该点左侧所有点的最大值和右侧所有点高度的最大值,然后这两个高度的最大值是决定在该位置能装多少雨水的关键。再根据大家都知道的木桶原理,再取这两个最大值的最小值,该最小值就决定了在该位置能装多少水。

举个简单的例子,对于该题的height[5],先找到它的左侧最大值是2,右侧最大值是3。然后2和3里面最小值是2,2再减去height[5],即2 - 0 = 2,为该位置(即height[5])的地方能装雨水的最大值。

其实如果只是要做出来,没有空间复杂度的要求,还是比较容易的。因为对于上面的思路,我们可以两个数组分别来存储每个点左侧的最大值和右侧的最大值。这样我们可以在O(1)的时间复杂度找到每个点两侧的最大值。然后找到最大值之后,再进行相减的操作即可。

如下是一段示例代码:

class Solution {
public:
    // 空间复杂度为O(N),时间复杂度O(N)

    //开辟两个数组a,b,用来分别存储每个位置左边的最大值和右边的最大值
    int a[20000 + 5], b[20000 + 5];
    int trap(vector<int>& height) {
        int ans = 0;
        int left = 0, right = height.size() - 1;
        
        //初始时为0
        int left_max = 0;
        for(int i = 0;i < height.size(); i++)
        {
            a[i] = left_max;
            left_max = max(left_max, height[i]);
        }

        //初始时为0
        int right_max = 0;
        for(int i = height.size() - 1; i >= 0; i--)
        {
            b[i] = right_max;
            right_max = max(right_max, height[i]);
        }

        //然后左右选最小的,减去当前的,算当前的乘雨水的值
        for(int i = 0; i < height.size(); i++)
        {
            int CanSave = min(a[i], b[i]);
            int cur = max(CanSave - height[i], 0);
            ans += cur;
        }
        return ans;
    }
};

那么官方的解法,让两个指针从两边往中间走,把O(N)的空间复杂度优化到了O(1)。实际上,这还是一种基于贪心的思想。

在LeetCode官方解答中并没有说明为什么,只是把这种做法解释清楚了。我们这里再去解释下为什么。

两个指针从右往左走,感觉和第二题又有点相似了。

思路是:两个指针,开始时在两边,让其分别往中间走。每次走的都是矮的那个。

然后维护两个变量RightMax和LeftMax(即右侧最大值和左侧最大值,每次右侧指针往中间走时,更新维护RightMax,左侧指针往中间走时,更新维护LeftMax),分别表示当前右侧的最大值和当前左侧的最大值。

那为什么可以这么做呢?我们都说,贪心的验证靠提交,那一种说法就是这么做一提交ac了哈哈,那自然这么做就是可以的。

那如果要解释,这种做法为什么可以呢?

首先,我们还是得要用和上一题类似的思想,以每个点的能够接到的雨水量为观察点。

那每个点能够接到的雨水量都是取决于   该点左侧最大值和右侧最大值   的最小值。(我们在刚刚的O(N)的空间复杂度里已经分析过)

(图思路分析)

我们假设一种情况吧,假如此时左边的矮,那左边的指针要往中间走,然后此时要计算的是左边Left指针位置能够接到的雨水量。

计算Left指针指向的点的(我们假设为i)雨水量的公式:min(左侧最大值,右侧最大值) - height[i]。

那左侧最大值就是LeftMax,我们现在要分析右侧最大值是不是RightMax呢?

左侧最大值 = LeftMax;
右侧最大值 = ?

因为在Right指针右边,是不可能有数超过RightMax的,所以我们只需要分析Right指针左边的即可,也就是分析[Left, Right]两个指针之间的数。

假如说,在中间存在有三种情况:

1、比height[Right]高。

2、如果比height[Right]矮,但是比height[Left]高。

3、比height[Left]还要矮。

讨论情况如下:

仍然分成三种情况讨论:

1、比height[Right]高,由于木桶原理,取决于能接多少雨水的是矮的柱子,而height[Left] < height[Right] < height[1],所以,1的情况并不会改变Left位置的接雨水的量。

2、如果比height[Right]矮,但是比height[Left]高。同1。

3、比height[Left]还要矮。由于站在Left的位置处考虑,RightMax不变(意思是,Left位置接雨水的量取决于min(左侧最大值,右侧最大值),但此时右侧最大值的没有改变的,依旧是RightMax),所以此时计算结果也不会变。

因此,我们如果使用RightMax这个值作为右侧最大值,这三种情况都不会改变Left指针指向的点的(我们假设为i)雨水量。因此,我们可以用RightMax来替代那个右侧最大值。

所以,我们的思路分析就结束了,这种方法可行。具体思路步骤参考【图思路分析】

我们最后给出我们的代码:

class Solution {
public:

    int trap(vector<int>& height) {
        int ans = 0;
        int left = 0, right = height.size() - 1;
        
        int LeftMax = 0, RightMax = 0;

        while(left < right)
        {
            //先更新LeftMax和RightMax
            LeftMax = max(LeftMax, height[left]);
            RightMax = max(RightMax, height[right]);

            //每次走矮的
            if(height[left] < height[right])
            {
                //走之前算出该点能够接的雨水的量,注意不可以小于0
                ans += max(LeftMax - height[left], 0);
                
                //然后往中间走
                left ++;
            }
            else
            {//同理即可
                ans += max(0, RightMax - height[right]);
                right --;
            }

        }
        return ans;
    }
};

可以看出,这个双指针,在这几道题中,往往会和贪心或者说剪枝(如果把枚举看成是暴力搜索)结合起来。它并不仅仅拘泥于快慢指针的题。还有像两个指针从两边走然后往中间相遇的问题,关键在于怎么走以及这么走能不能行、对不对的问题。这类问题说实话,我自认为没有什么好的模板,唯一的做法就是 多练习吧哈哈哈,当你见过了数种双指针的走法之后,你看到这些题目就自然而然地、或多或少的有点思路了。

好了,我是自学编程村村长,如果你想自学编程,欢迎大家关注微信公众号 自学编程村。

下一节,我们会跳一下,因为觉得前面这些题可能没什么模板,没有什么可讲性哈哈,下面一节将会为大家带来图和树的有关知识。

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

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

相关文章

功能实现——通过阿里云 OSS 实现文件管理

目录 1.需求分析2.阿里云 OSS 开通与配置2.1.登录阿里云官网2.2.搜索 OSS 服务并开通2.3.OSS 配置 3.在项目使用阿里云 OSS3.1.项目环境搭建3.2.代码实现3.2.1.将本地文件上传到阿里云 OSS3.2.2.将前端传入的文件上传到阿里云 OSS3.2.3.下载文件到本地2.3.4.流式下载3.2.4.OSSC…

携程酒店、景点、评论数据采集,testab动态js加密参数处理不补js环境不纯算

携程平台的酒店、景点、评价数据还是比较有意义的&#xff0c;可用作数据分析研究&#xff0c;目前均可采集。酒店价格需要登录采集&#xff0c;其他店名、地址、经纬度、评分、评价数、特色标签等都可以直接采集。 携程酒店数据示例&#xff1a; 酒店id&#xff1a;536424 …

免费【2024】springboot 大学生在线论坛系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

常用配置文件 .ini、.config、 json、xml

ini Initialization INI文件格式是某些平台或软件上的配置文件的非正式标准&#xff0c;以节(section)和键值对(key-val)构成&#xff0c;常用于微软Windows操作系统中。 这种配置文件的文件扩展名多为INI 》》INI文件不支持数据类型&#xff0c;所有的键值对都是字符串类型…

[SQLSERVER][SQL]数据库备份任务异常

SQL备份异常出现如下错误&#xff1a; 执行查询“BACKUP DATABASE [ReportServerTempDB] TO DISK N...”失败&#xff0c; 执行查询“BACKUP DATABASE [ReportServerTempDB] TO DISK N...”失败&#xff0c; 错误如下:“无法打开备份设备 E:\\sqldatabackup19\\Report_ba…

nose2,一个无敌的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个无敌的 Python 库 - nose2。 Github地址&#xff1a;https://github.com/nose-devs/nose2 在软件开发过程中&#xff0c;自动化测试是保证代码质量的重要环节。Python 提供…

Python(模块)

模块编写完成就可以被其他模块进行调用并使用被调用模块的功能。 import导入方式的语法结构&#xff1a; import模块名称【as别名】 from……import导入方式的语法结构&#xff1a; from模块名称&#xff0c;import变量/函数/类/*&#xff08;*是通配符&#xff09; impor…

非线性磁链观测器

前面已经介绍了龙伯格观测器、滑膜观测器&#xff1a;龙伯格观测器&#xff08;又称状态观测器&#xff09;、滑膜观测器-CSDN博客 滑膜观测器和龙伯格观测器都是没法实现电机的超低速运转、0速启动、超高速的正反方向的运行。龙伯格观测器是可以实现这些功能&#xff0c;但是…

Resolving Maven dependencies

Maven是一种项目管理和构建工具&#xff0c;通常用于Java项目。这个过程包括下载项目所需的所有外部库和插件&#xff0c;并将它们添加到项目的构建路径中。具体来说&#xff0c;它正在处理名为“AAS_byBasyx”的项目或模块的依赖项。这种任务通常在你打开一个新的Maven项目或更…

基于web的购物网站的设计与实现(系统源码+lw+部署文档+讲解等)

文字目录&#xff1a; 目录 详细视频演示 系统实现界面 1.1系统开发环境以及运行环境 1.1.1系统开发环境 1.1.2系统运行环境 1.2系统功能实现 1.3管理员模块实现 2 技术介绍 2.1 thinkphp5介绍 2.2 MySQL数据库 2.3 B/S结构 4.1系统结构设计 4.2系统功能结构设计…

​JavaWeb入门——Servlet(二)

目录 HttpServletRequest 简介 常见API HttpServletResponse 简介 常见API 请求转发和响应重定向 概述 请求转发 响应重定向 cookie&session 会话 会话跟踪方案&#xff1a; 会话跟踪方案对比&#xff1a; 1、Cookie 2、Session&#xff08;底层基于Cookie…

RocketMQ5.0消费者

RocketMQ 5.0 提供了三种主要的消费者类型&#xff1a;PushConsumer、SimpleConsumer 和 PullConsumer。每种类型的消费者都有其特定的使用场景和特点。以下是对这三种消费者的概念及其区别的详细阐述&#xff1a; PushConsumer 概念&#xff1a; PushConsumer 是一种主动推送…

Python time模块5大隐藏特性

特性一&#xff1a;结构化时间——struct_time 简介&#xff1a; struct_time是time模块中的一个数据类型&#xff0c;用于存储一个时间的各个组成部分&#xff08;年、月、日、时、分、秒等&#xff09;。它常用于解析和格式化时间。 代码示例&#xff1a; import time # …

GA4(Google Analytics 4 )升级指南丨出海笔记

Google Analytics 4 是与之前的Universal Analytics/GA3完全不同的全新版本&#xff0c;2023下半年(7月后)会全面替换UA。 本篇指南将会展示如何安装和使用 Google Analytics 4 (GA4), 以便大家更好的迁移并使用新平台。相比GA3, GA4绝对是好东西&#xff0c;这次的内容比较硬核…

Linux基于centOS7【内存与OS的随谈】,进程初学【PCB】【fork】【进程排队】

冯诺依曼体系结构——存储器 存储器主要指的是内存&#xff0c;它有个特点就是掉电易失 磁盘等其它输入和输出设备 为什么要在计算机体系结构中要存在内存 我们知道&#xff0c;CPU的处理速度很快很快&#xff0c;但输入设备&#xff0c;以及输出设备&#xff0c;是相对很慢的…

C#对Sqllite操作

前言 数据库的操作也是程序设计中的家常便饭了&#xff0c;关系型数据库中Sqlite3是非常轻量级别的&#xff0c;所以这个数据在小型应用程序的设计中占用很高的比重。当然我这里描述的是1.0版本&#xff0c;也是最原始的方案&#xff0c;大型应用开发中一般选择EF进行桥接&…

信息学奥赛初赛天天练-54-CSP-J2019阅读程序3-二叉树、满二叉树、单侧二叉树、二分查找、递归、等差数列求和

PDF文档公众号回复关键字:20240803 2019 CSP-J 阅读程序3 1阅读程序(程序输入不超过数组或字符串定义的范围&#xff1b;判断题正确填 √&#xff0c;错误填 。除特殊说明外&#xff0c;判断题 1.5 分&#xff0c;选择题 3 分&#xff0c;共计 40 分) 01 #include <iostre…

idea项目创建提交到gitee gitee创建仓库 gitee删除仓库(全网最新最详细)

一、gitee创建仓库 1.如下图 2.创建好后如下图 3.打开idea创建好项目 3.1点击终端 3.2 从gitee页面复制命令进行运行 具体步骤如下图&#xff1a; 在步骤5时可能会提醒你远程仓库没有main分支&#xff0c;这个时候需要执行下图中的命令4创建一个远程main分支 结果运行如下图…

GATK ReferenceDataSource接口介绍

在 GATK(Genome Analysis Toolkit)库中,ReferenceDataSource 接口是一个重要的接口,用于表示与参考基因组相关的数据源。它提供了一种标准化的方式来访问和操作参考基因组的不同来源的数据。ReferenceMemorySource 类和ReferenceFileSource 类是ReferenceDataSource接口的实…

给本地设备搭建一个云端语音助手

概述 本语音助手实现了从关键词唤醒 (KWS) 到语音识别 (ASR) 再到自然语言理解 (NLU) 的完整流程。该系统可以通过监听用户的音频输入,检测指定的关键词,并将用户的语音转换为文本,最后与预设的命令进行匹配,执行相应的操作(具体实现请参考main.py),为你的设备配置远程…