算法解析——双指针算法(3)数据匹配

news2024/11/26 5:50:45

欢迎来到博主的专栏——算法解析
博主ID:代码小豪


文章目录

    • LCR179、查找总价格为目标值的两个商品
    • leetcode11——盛水最多的容器
    • leetcode611——有效三角形的个数

我们前面已经盘点了两种双指针算法的使用场景,现在来看看最常见的一种。

通常,我们遇到这种题型,即在一个数组中,找出任意两两数据,以达到某种条件(有时会要求三个)。博主在这里选择了比较典型的例子

LCR179、查找总价格为目标值的两个商品

在这里插入图片描述
解法一:暴力解法
两个for循环遍历出所有数据相加的结果,如果结果等于target,直接返回这个结果。

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        for (int i = 0; i < price.size(); i++)
        {
            for (int j = i + 1; j < price.size(); j++)
            {
                if (price[i] + price[j] == target)
                    return vector<int>({ price[i] ,price[j] });
            }
        }
        return vector<int>();
    }
};

实际上这种暴力解法也算是双指针,毕竟出现了两个下标i,j来指向数组中的数据。当然了这不是我们想要的双指针算法。因为效率太慢了

像这种在数组中找两个数据匹配的问题,暴力枚举肯定是有效的,只是时间复杂度太高,一旦数据项变多,那么可能就会出现超时的情况。

这里我们就要对枚举这个方法进行思考了。既然暴力枚举不可,那么有没改进的方法呢?当然有,这需要我们对条件进行思考了。

枚举这个方法不是不行,只是暴力枚举不太行,比如你的妈妈让你去买鱼,你会从五金店开始找吗?当然不会,因为直接到菜市场或者渔市上寻找会更加简单。如果我们在枚举的过程中,加上筛选掉不可能的选项,那么枚举的效率就会大幅提高。以此题为例,如果一个数大于两数相加的结果。那么这个数一定不会是需要加入枚举的选项,将其排除即可。这会提高我们的运行效率的当然,这个方法不是此题的解法。

我们先注意一下题目,看看这些数据有没有什么规律?

没错,这个数组是升序排序的。那么双指针可以对这个升序排序的数组做什么呢?
我们假设现在有一个由a1,a2,……a10十个升序的数据组成的数组。设目标值为s,现在取a3和a8求和,得到值s1,假设s1不等于s,那么s1的结果只能是大于或小于s。
在这里插入图片描述
拿情况1举例,如果a3+a10>s,那么我们还需要判断a4+a10,a5+a10,……,a9+a10的结果吗?当然不用了,因为这是一个升序的数组,a3<a4<a5<…<a10,如果a3+a10都大于s了,那么a4+a10,a5+a10一定都是大于s的,因此我们不需要再枚举a3以后的数据与a10相加的结果是否等于s,而是应该判断a3+a9是否等于s。

拿情况2举例,同理,如果a3+a10<s了,那么a3+a9,a3+a8,a3+a7……都会小于s,因此也不用判断a10以前的数据与a3相加的结果是否等于s,而是要判断a3+a9是否等于s。

那么解决方法就很明确了,我们使用双指针算法来进行数据匹配,方法如下:
(1)指针left指向数组的起点,指针right指向数组的末尾
(2)如果left+right的结果<target,left++。
(3)如果left+right的结果>target,right–。

原理如下:
如果left+right的结果<target,这说明right之前的数据与left相加都会小于target,不再对right之前的数据与left进行匹配,而是让left++。
如果left+right的结果>target,这说明left之后的数据,与right相加都会大于target,不再对left之后的数据与right进行匹配,让right–。
在这里插入图片描述

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int left = 0;//左指针
        int right = price.size() - 1;//右指针
        while (left < right)//数据匹配
        {
            if (price[left] + price[right] < target)//左右数据之和小于s
                left++;
            else if (price[left] + price[right] > target)//左右数据指针大于s
                right--;
            else//找到目标值
                return vector<int>{price[left], price[right]};
        }
        return vector<int>();//如果没找到,就返回空容器
    }
};

leetcode11——盛水最多的容器

在这里插入图片描述
在这里插入图片描述
这道题看起来比上一道难多了,但实际上解题思路都是一样的。我们先来了解一下题目到底要求我们求什么?

假设现在有a1,a2,a3,a4,……a10十个数据组成的无序数组,任取两个数据
在这里插入图片描述
根据容器的体积公式v=s*h,其中h为两个数据的最小值,s为两个数据之间的距离。

在上例中,h为5,s=4.因此v=4*5=20。请牢牢记住这个公式,很重要。

解法1,暴力枚举。
暴力枚举的方法为,使用两个for循环,枚举数组中任意两两数组的组合的容积结果,然后取最大值。这个方法很挫,我就不把代码放出来了,反正也过不了leetcode的测试用例(因为超时)。

解法2,双指针算法
我们要知道的一点是,使用双指针算法来进行数据匹配的原理是,优化暴力枚举,通过排除一些不必要的数据,减少枚举的的结果,起到一个优化暴力枚举的作用,这也是博主为什么要再双指针解法之前提一嘴暴力枚举的原因。

那么我们来看看这道OJ题可以在哪个方面进行优化。还记得上道题是怎么做的吧?我们很好的利用了数组升序这一特点,找到了数据之间的特性。那么这个数组的数据有什么特性呢?

首先,我们先来找找这些数据的单调性,它们有序吗?没有,那么难道这道题就没有解法了吗?

当然不是,我们先来这些数据是什么?这些数据代表的是容器一边的高度,在这一组数据当中,高度是无序的,但是求容器的体积不单单需要考虑它的高度,也要考虑它的底。我们再来回顾一下容器的的体积公式

v=s*h

当h不变时,s变小,v就变小。如果h变小了,s也变小了,那么v还是变小。

诶,这不就是找到数据的单调性了吗?我们设一指针为left,指向数组的起始位置,一指针为right,指向数组的末尾位置,如果left和right逐渐靠近,那么s是逐步变小的,因为s=right-left。那么我们只需要判断,在left和right逐渐靠近时,容器的h是否变大了,因为h无论是不变还是变小,s变小了,体积都会变小。

解题步骤如下:
(1)让left指向数组的起始位置,right指向数组的末尾位置。
(2)比较left和right的数据,数据小的为容器的高度。记录一下当前的容积
(3)让较小数据的一方进行移动,比如left<right,则left++,left>right,则right–。
(4)如果有一出现了更大的高度,就要算出它的容积,并且与当前最大容积进行比较
(5)当left和right相遇时,返回当前最大容积。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0;
        int right = height.size() - 1;
        int max_high = min(height[left], height[right]);//求高度
        int max_area = max_high * (right - left);//当前最大容积

        while (left < right)
        {
            int high = min(height[left], height[right]);//较小的值作为容器的高
            if (high > max_high)
            {
                max_high = high;
                int new_area = high * (right - left);//计算新的面积是否是最大值
                max_area = max_area > new_area ? max_area : new_area;
            }
            if (height[left] > height[right])//哪边的数值小,就让那边的指针进行移动
                right--;
            else
                left++;
        }
        return max_area;
    }
};

leetcode611——有效三角形的个数

在这里插入图片描述
先来理解一下题目。
在一个数组当中,任取三个元素组合成一个三元组,这三个元素需要符合构成三角形的三边边长,计算一个数组中有多少个这种三元组。

我们先回顾一个中学知识。那就是三角型较小两边之和,必须大于最大的一条边。这是一个解题的关键。

解法1:
暴力枚举,枚举所有组合出来的三元组,并且判断这些三元组构成三角形的数量

解法2:
双指针算法。

实际上这道题的双指针算法比较取巧,因为这个数组本身是无序的,而博主先对整个数组进行排序,通常来说,我们在编写程序时,除非要求需要对数据进行修改,否则尽量不修改数据。只作判断。

当然,我们可以将数据拷贝到另外一个容器当中,对新拷贝容器就行排序,计算,然后返回计算结果,这样也不会对旧容器的数据进行修改。

将数组排成升序有什么好处,我们回顾一下判断三角形的要求

三角型较小两边之和,必须大于最大的一条边。

要知道一点是,如果我们要筛选三元组的数据,那么双指针一定是不够的,因为双指针最多指向两个数据,而三元组是由三个数据组成的。如果我们进行了升序的排序,可以得到这么一个结果。
在这里插入图片描述
没错,我们找到了最长的一边,因为数据已经排成升序了,数据从后向前是依次递减的,因此,我们可以假设某条边是最长边。
在这里插入图片描述
因为该数是最长边,因此与它组成三元组的其他两条边都在区间[left,right]以内。因为既然选择红色这边作为最长边,那么红色之后的数据比红色长,那么显然是不对的,因为选取了红色作为最长边。

那么现在已经解决了一个问题了,知道了最长边是多少,但是剩下两个边长该如何选取呢?
其实在图中已经给出答案了,我们假设right指向的是次长的边,left指向最短边。OK,那么解决方式已经非常简单了,因为left+right如果大于最大边,那么就不用计算left之后的数据与right组合的结果了,因为升序的原因,left之后的数据都会比left大。直接让构成三元组的个数+=right-left。之后让right–。
在这里插入图片描述

为什么要让right–呢?因为我们已经明确了这个规则,最长边,次长边不动,计算最短边和次长边之和时候大于最长边。但是次长边是可以变化的啊,因此要让right–。
在这里插入图片描述

如果right与left相遇了,那就枚举完当前区间与最长边构成三角形的所有情况,让其他的数据做最长边。
在这里插入图片描述

(1)我们设最长边为max_edge,让left指向数组的起始位置,right指向max_edge的前一个数据。
(2)计算left+right>max_edge的结果,若left+right>max_edge,就计算[left,right)之间的数据个数,反之,left++
(3)计算完[left,right)之间的个数后,让right–,重复第二步
(4)如果left和right相遇了,就让max_edge–,重复第1步
(5)如果max_edge来到了数组的起始位置,那么枚举结束

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());//将数组排成升序
        int max_edge = nums.size() - 1;//最长边
        int count = 0;//构成三角形的三元组计数

        while (max_edge > 1)
        {
            int left = 0;
            int right = max_edge - 1;
            while (left < right)
            {
                if (nums[left] + nums[right] > nums[max_edge])//较小两边之后大于第三边
                {
                    count += right - left;
                    right--;
                }
                else
                    left++;
            }
            max_edge--;
        }
        return count;
    }
};

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

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

相关文章

社交电商系统开发功能定制

社交电商系统开发功能定制是一项综合性的服务&#xff0c;它涉及到需求分析、界面设计、功能开发、系统测试以及上线后的持续维护。以下是社交电商系统开发中应考虑的关键功能点&#xff1a; 需求分析和规划&#xff1a;开发前&#xff0c;企业需要明确业务需求&#xff0c;包…

【数据分析:一篇文章带你掌握Pandas高级可视化技巧】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索数据分析的数据可视化&#xff0c;本篇文章主要讲述了&#xff1a;掌握Pandas高级可视化技巧等等。欢迎大家一起探索讨论&#xff01;&#xff01;&#xff01…

docker的swarm技术

docker中swarm技术 docker swarm技术是docker社区提供的docker的集群管理调度工具&#xff0c;通过api来管理多个主机上的docker&#xff0c;通过overlay网络来实现不同主机之间容器的通信与访问。实现容器的调度&#xff0c;资源的分配&#xff0c;以及副本。 docker swarm中…

愛ㄉ粗麵體字體欣赏

愛ㄉ粗麵體字體字体欣赏&#xff0c;英文名AddaNoodle 下载到字体档后&#xff0c;首先打开字体文件预览看看&#xff0c;超可愛的&#xff5e; 最近流行的可愛字體&#xff0c;VIVISTICKER-愛ㄉ粗麵體 QQ 軟軟的筆觸不只適合有溫度的限動 放在貼文或介紹文字都超級適合 還找不…

汇编语言指令 jmp: jmp word ptr、jmp dword ptr、jmp 寄存器、jmp 段地址:偏移地址

1. 转移地址在内存中的jmp指令有2种形式 1.1 jmp word ptr 内存单元地址 jmp word ptr 内存单元地址是段内转移指令&#xff0c;也就是说该指令只修改IP值&#xff0c;其功能是控制CPU下一条执行的指令是一个字&#xff08;2个字节&#xff09;内存中存放的偏移地址所指向的指…

【Python】函数入门(下)

3&#xff09;&#xff09;* ** ​​​​​​注意&#xff1a;也遵循位置传参在前面&#xff0c;按关键字传参在后面。 代码示例&#xff1a; def func(*args,**kwargs):print(args,kwargs) 该函数中的参数会自动根据传参的方式不同&#xff08;即&#xff1a;按位置…

家电行业实施数字工厂管理系统前要做好哪些准备

家电企业在实施数字工厂管理系统前&#xff0c;需要做好以下准备&#xff1a; 一、明确目标与需求 数字化转型目标&#xff1a;明确数字化转型的具体目标&#xff0c;如提高生产效率、降低生产成本、优化库存管理、提升产品质量等。这些目标将指导整个数字工厂管理系统的选择…

STM32学习笔记09-SPI通信

目录 SPI通信简介 硬件电路 移位示意图 SPI基本时序单元 SPI时序 W25Q64简介 硬件电路 W25Q64框图 Flash操作注意事项 SPI外设简介 SPI框图 SPI基本结构 主模式全双工连续传输 非连续传输 软件/硬件波形对比 SPI应用 软件SPI读写W25Q64 硬件SPI读写W25Q64 SP…

在复制 PowerPoint (PPT) 文件时,其中嵌入或者链接的 Excel 文件能够自动创建一个新的实例,而不是继续引用原始的 Excel 文件。

问题&#xff1a; 在复制 PowerPoint (PPT) 文件时&#xff0c;其中嵌入或者链接的 Excel 文件能够自动创建一个新的实例&#xff0c;而不是继续引用原始的 Excel 文件。 解答&#xff1a; 导入已有的excel实测是不行的

为什么程序员应该学习多种语言?

前言 有一句老话适用于所有软件开发人员&#xff1a;知道得越多&#xff0c;成长得越多。程序员学习多种编程语言后、‌可以加速学习新技术、‌拓展认知和视野。‌ 对于程序员来说&#xff0c;‌精通一门编程语言意味着对该语言有深入的理解和熟练的运用&#xff0c;‌这为程…

LLMCompiler执行数学计算的案例

LLMCompiler执行数学计算的案例 LLMCompiler执行数学计算的案例案例代码LLMCompiler的LangGrap结构可视化Tasks DAG可视化完整运行日志 LLMCompiler执行数学计算的案例 这是一个使用LLMCompiler执行数学运算的完整案例&#xff0c;通过设置’ print_dag 参数&#xff0c;可以完…

vme背板 介绍

VME背板是基于VME总线规范的背板,它是VME系统中的重要组成部分,主要负责模块之间的连接和数据传输。VMEbus(Versa Module Eurocard bus)是一种广泛应用于工业控制、军事、航空航天以及电信领域的模块化计算机总线标准。VME背板是VME系统的核心组件之一,它不仅提供了物理支…

思路超清晰的 LVS-NAT 模式实验部署

目录 一、实验原理 1、实验基础配置图 2、实验原理 二、实验环境准备 1、准备四台红帽9的主机 2、四台主机的基础配置 &#xff08;1&#xff09;client 1&#xff09;配置主机名&#xff1a;client 2&#xff09;配置ip:172.25.254.200 &#xff08;2&#xff09;lv…

DAMA学习笔记(十四)-数据管理成熟度评估

1.引言 能力成熟度评估&#xff08;Capability Maturity Assessment&#xff0c;CMA&#xff09;是一种基于能力成熟度模型&#xff08;Capability Maturity Model&#xff0c;CMM&#xff09;框架的能力提升方案&#xff0c;描述了数据管理能力初始状态发展到最优化的过程。CM…

掌握VS Code调试技巧:解决Scrapy模块导入中断问题

介绍 在使用Scrapy进行网页爬取时&#xff0c;Visual Studio Code&#xff08;VS Code&#xff09;作为一款流行的代码编辑器&#xff0c;凭借其强大的调试功能&#xff0c;成为了许多开发者的首选。然而&#xff0c;部分开发者在调试Scrapy爬虫时&#xff0c;常遇到程序在模块…

《剑指offer》题目 C++详细题解

JZ15 二进制中1的个数 核心考点&#xff1a;二进制计算 思路一&#xff1a;使用一个循环&#xff0c;因为我们知道整型变量只有32位&#xff0c;所以循环结束的条件就是到32&#xff0c;从最低位开始&#xff0c;逐位检查数字 n 的二进制表示&#xff0c;利用位运算中的与运算…

Ubuntu22.04自动化安装Redis脚本(实测可用)

redis自动化安装脚本 #!/bin/bash# 检查 Redis 是否已安装 if dpkg -l | grep -q redis-server; thenecho "Redis 已安装"exit 0 elseecho "Redis 未安装&#xff0c;正在安装..."# 更新包列表sudo apt-get update# 修复系统中的破损依赖关系sudo apt --f…

教你用JQ怎么循环遍历数据,学会直接月入过万,不够我给你补!

1.先看数据类型。这是一个标准得json返回值 {"code": 200,"msg": "请求成功&#xff01;","data": [{"itemName": "给阿姨倒一杯卡布奇诺","unit": "颗","count": 6},{"item…

c语言 图片.bmp读写示例

1 图片.bmp数据结构 BMP&#xff08;Bitmap&#xff09;文件格式是一种简单的位图图像格式&#xff0c;其数据结构分为几个主要部分&#xff1a;文件头、信息头、调色板&#xff08;可选&#xff09;和像素数据。下面是各部分的详细说明。 文件头&#xff08;File Header&…

zabbix看图表的时候标题是乱码

直接进入到&#xff1a;/usr/share/zabbix/assets/fonts 然后进入到windows下边fonts选择一个自己喜欢的字体&#xff0c;上传到/usr/share/zabbix/assets/fonts 然后把内容graphfont.ttff覆盖即可