【c++算法篇】双指针(下)

Alt

🔥个人主页Quitecoder

🔥专栏算法笔记仓

Alt

朋友们大家好啊,本篇文章我们来到算法的双指针的第二部分

目录

  • `1.有效三角形的个数`
  • `2.查找总价格为目标值的两个商品`
  • `3.三数之和`
  • `4.四数之和`
  • 5.双指针常见场景总结

1.有效三角形的个数

题目链接:611. 有效三角形的个数
题目描述
在这里插入图片描述

这道题当然可以暴力求解,三层循环枚举所有情况,来进行判断,但是可以进行优化:

我们知道,三角形的满足条件是任意的两边之和大于第三边,但是如果我们已经判断了较小的两个边大于第三边,就不需要再进行剩下两组的判断,所以我们先进行排序,再进行枚举:

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

具体讲解一下我们的思路:

这里使用的是一种双指针技术:固定最长的边(也就是数组中的最大值),使用两个指针来查找剩余部分中可能的两个较短边。如果找到了两个较短边的长度和大于最长边,那么这三者能构成一个三角形

class Solution {
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(),nums.end());int count=0;for(int i=nums.size()-1;i>=2;i--){int lat=i-1,pre=0;while(pre<lat){if(nums[pre]+nums[lat]>nums[i]){count+=lat-pre;lat--;}else pre++;}}return count;}
};

它利用了一个重要的性质:如果你有三条边长分别为 a, b 和 c,且 a ≤ b ≤ c,那么 a, b 和 c 可以构成一个三角形当且仅当 a + b > c

步骤如下:

  1. 对数组 nums 进行升序排序
  2. 初始化计数器 count 为 0
  3. 从后往前遍历数组(从最大值开始,下标为 i),我们将这个值作为潜在的最长边 c
  4. 对于每一个 c,设置两个指针:pre 指针指向数组的开始(下标为 0),lat 指针指向 c 之前的元素(下标为 i - 1
  5. pre 指针小于 lat 指针时:
    • 计算 nums[pre]nums[lat] 的和,将这个和与 nums[i](也就是当前的 c)进行比较
    • 如果 nums[pre] + nums[lat] > nums[i],由于数组已经排序,所有在 prelat 之间的元素与 nums[lat] 的和都会大于 nums[i],所以我们可以将 lat - pre 个三角形加到 count
    • 然后将 lat 向左移动一位(减小一点以寻找下一个可能的三角形)
    • 如果和小于等于 nums[i],我们将 pre 向右移动一位(增大一点以寻找可能的三角形)
  6. 当处理完所有的 c 后,返回 count 作为结果

本道题还是很简单的

2.查找总价格为目标值的两个商品

题目链接:LCR 179.查找总价格为目标值的两个商品
题目描述在这里插入图片描述

算法的具体思路:

  1. 初始化两个指针,pre 指向数组的开始(索引 0),last 指向数组的末尾(索引 price.size() - 1
vector<int> s1;
int last=price.size()-1;
int pre=0;
  1. 进行一个 while 循环,在数组两端移动 prelast 指针直到它们相遇。循环的条件是 pre < last,确保没有重复使用相同的元素。

  2. 在每次循环中,计算两个指针指向的数的和,判断这个和与目标值 target 的关系:

    • 如果和大于 target,那么为了减小和,last 指针左移(减小索引值)
    • 如果和小于 target,那么为了增大和,pre 指针右移(增加索引值)
    • 如果和等于 target
      • 将这两个数添加到结果 vector s1 中。
      • 因为只需要一组解,所以找到一对满足条件的数之后,通过 break 语句退出循环
while(pre<last)
{if(price[pre]+price[last]>target)last--;else if(price[pre]+price[last]<target)pre++;else {s1.push_back(price[pre]);s1.push_back(price[last]);break;}
}
  1. 返回结果 vector。如果找到至少一对和为 target 的数,s1 会包含这两个数。如果没有找到,s1 将是空的

完整代码如下:

class Solution {
public:vector<int> twoSum(vector<int>& price, int target) {vector<int> s1;int last=price.size()-1;int pre=0;while(pre<last){if(price[pre]+price[last]>target)last--;else if(price[pre]+price[last]<target)pre++;else {s1.push_back(price[pre]);s1.push_back(price[last]);break;}}return s1;}
};

3.三数之和

题目链接:15.三数之和
题目描述在这里插入图片描述

对于三数之和,我们大思路如下:

对于示例在这里插入图片描述
我们首先进行排序:
在这里插入图片描述
然后,首先固定第一个数,只需要在后面的数中找到两个数使三个数相加和为0即可

对于后面的数的寻找,我们可以设置前后指针,如果三数之和大于零,则让较大的数减小点,即右指针左移,三数之和小于零,则让左指针右移,如果等于零,则讲这三个数据插入到目标数组中继续遍历

注意,上面的{-1,0,1}这三个数是可以构成目标数的,但是必须跳过其中一个-1,因为不能重复

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(),nums.end());for(int i=0;i<nums.size()-2;i++){if(i>0&&nums[i-1]==nums[i])continue;int pre=i+1,las=nums.size()-1;while(pre<las){if(nums[pre]+nums[las]<(-nums[i]))pre++;else if(nums[pre]+nums[las]>(-nums[i]))las--;else{result.push_back({nums[i],nums[pre],nums[las]});while(pre<las&&nums[pre+1]==nums[pre])pre++;while(pre<las&&nums[las-1]==nums[las])las--;pre++;las--;}}}return result;}
};

注意的要点:

  1. 唯一性:返回的结果中不能包含重复的三元组。解决方法是在找到一个符合条件的组合后,跳过所有相同的元素

  2. 遍历策略:外层循环遍历数组,内层使用双指针从两端向中间查找两个其他元素,以保证三个数的和为零

  3. 跳过重复元素

  • 在外层循环中,如果当前的数字与前一个数字相同,则跳过以避免重复的三元组
 for(int i=0;i<nums.size()-2;i++)
{if(i>0&&nums[i-1]==nums[i])continue;
  • 在找到一个满足条件的三元组之后,同时跳过 pre 指针的连续重复数字,并将 pre 指针向右移动
  • 同样地,跳过 las 指针的连续重复数字,并将 las 指针向左移动
  1. 寻找条件:三数之和等于零。这意味着在内层循环中,如果 nums[pre] + nums[las] 小于 -nums[i],则需要右移 pre 指针;如果大于 -nums[i],则需要左移 las 指针;如果等于 -nums[i],则记录该三元组,继续寻找其他可能的组合

  2. 边界条件

    • 外层循环的循环变量 i 应小于 nums.size() - 2,因为需要至少3个数来组成一个三元组
    • prelas 指针相遇时,内层循环结束

我们还可以进一步优化,当i对应的数字大于零,意味着无论如何结果都大于零,就可以直接break了:

for(int i=0;i<nums.size()-2;i++)
{if(i>0&&nums[i-1]==nums[i])continue;if(nums[i]>0)break;

4.四数之和

题目链接:18.四数之和
题目描述在这里插入图片描述

这道题与上面三数求和大体思路一样,我们这次一次固定两个数,然后再遍历剩下的数,遇见相同的数就往后移动

注意
上道题数组长度是大于等于3的,而这道题nums数组长度大于等于1,意味着可能不存在四个数,所以首先我们先判断数组长度,如果小于四直接返回空数组

if(nums.size()<4)return{};

首先进行排序工作

接着开始完成函数内容,需要固定两个数,我们则需要嵌套两个循环,注意边界值即可:

vector<vector<int>> result;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size()-3; i++) {if (i > 0 && nums[i] == nums[i - 1]) continue; for (int j = i + 1; j < nums.size()-2; j++) {if (j > i + 1 && nums[j] == nums[j - 1]) continue; ——————————
}

这里处理逻辑与上面一样,先跳过相同的数,在j的循环中,我们就进行和上面相同的操作了

int pre = j + 1;
int last = nums.size() - 1;
while (pre < last) {long long sum = (long long)nums[i] + nums[j] + nums[pre] + nums[last]; if (sum < target) {pre++;}else if (sum > target) {last--;}else {result.push_back({ nums[i], nums[j], nums[pre], nums[last] });while (pre < last && nums[pre] == nums[pre + 1]) pre++; while (pre < last && nums[last] == nums[last - 1]) last--; pre++;last--;}
}

本题还有一个关键点

在这里插入图片描述
它提供的值不一定是整形,所以上面函数中我们使用长整型来避免溢出

总代码如下:

class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {if (nums.size() < 4)return{};vector<vector<int>> result;sort(nums.begin(), nums.end());for (int i = 0; i < nums.size() - 3; i++) {if (i > 0 && nums[i] == nums[i - 1]) continue; for (int j = i + 1; j < nums.size() - 2; j++) {if (j > i + 1 && nums[j] == nums[j - 1]) continue; int pre = j + 1;int last = nums.size() - 1;while (pre < last) {long long sum = (long long)nums[i] + nums[j] + nums[pre] + nums[last];if (sum < target) {pre++;}else if (sum > target) {last--;}else {result.push_back({ nums[i], nums[j], nums[pre], nums[last] });while (pre < last && nums[pre] == nums[pre + 1]) pre++;while (pre < last && nums[last] == nums[last - 1]) last--;pre++;last--;}}}}return result;}
};

5.双指针常见场景总结

双指针主要应用在有序数组或链表的问题中,以及一些可以通过前后关系来优化问题的场景:

  1. 有序数组的对撞指针

    • 两数之和:在有序数组中找到两个数,使它们的和为特定的目标值
    • 三数之和/四数之和:与两数之和类似,但需要找到三个或四个数的组合
    • 移除元素:从有序数组中移除重复项或特定值,并返回新数组的长度
  2. 快慢指针

    • 链表中环的检测:使用快慢指针检测链表是否有环,快指针一次移动两步,慢指针一次移动一步
    • 寻找链表中点:使用快慢指针找到链表的中间节点,快指针结束时慢指针在中点
    • 寻找链表的倒数第k个元素:快指针先移动k步,然后快慢指针共同移动,快指针到达末尾时慢指针所在位置即倒数第k个元素
  3. 前后指针

    • 归并排序中的合并步骤:使用两个指针分别指向两个有序数组的开始位置,以合并成一个新的有序数组。
    • 对链表进行操作:在链表上进行操作时,如删除节点或反转链表,常常需要前后指针来保持结点的连接。
  4. 左右指针

    • 二分查找:在有序数组中查找元素,使用左右指针限定查找范围

双指针方法的关键在于,指针的移动可以依据问题的规律来减少不必要的比较或计算,从而提高算法效率。当然,双指针的使用需要充分理解问题的性质,并巧妙设计指针的移动策略。在很多问题中,双指针技术都能将时间复杂度从 O(n2) 优化到 O(n),超级好用

本节内容到此结束!!感谢大家阅读!!

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

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

相关文章

解决 SyntaxError: Unexpected token ‘.‘ 报错问题

这个报错一般是编译问题&#xff0c;浏览器的版本过低没通过代码 解决办法&#xff1a; 在package.json文件中加上这个 "browserslist": ["> 1%","last 2 versions","not dead","not ie < 6","Android > 4&…

98、技巧-颜色分类

思路 这道题的思路是什么&#xff0c;首先典型荷兰国旗问题&#xff1a; 该问题的关键在于我们要将所有的0放到数组的前部&#xff0c;所有的1放在中间&#xff0c;所有的2放在后部。这可以通过使用两个指针&#xff0c;一个指向数组开头的“0”的最后一个位置&#xff0c;另…

如何确保UDP文件传输工具有最低稳定的传输速度?

在当前日新月异的数字时代背景下&#xff0c;文件传输工具已经成为我们日常生活与工作中不可或缺的一部分&#xff0c;尤其针对那些频繁涉及即时数据交互与多媒体流通的场景。 UDP协议&#xff0c;以其突出的高速传输与低延迟特性&#xff0c;脱颖而出成为众多用户的首选。不过…

Python管理PVE(Proxmox VE)云平台--节点资源统计

一、前言 写本脚本的初衷是因手动查看统计已分配的PVE资源过于耗时&#xff0c;因此写一个脚本一劳永逸&#xff0c;具体实现方法&#xff1a;利用Python的paramiko模块进行远程命令查看、统计PVE平台各节点已分配的cpu、内存、磁盘空间。 二、步骤 1.构建shell脚本 1.1 统计…

Ubuntu系统下编译OpenCV4.8源码

OpenCV4.8源码编译与安装 其实很简单&#xff0c;只要三步即可搞定&#xff0c;第一步是下载指定版本的源码包&#xff1b;第二步是安装OpenCV4.8编译需要的编译器与第三方库支持&#xff1b;第三步就是编译OpenCV源码包生成安装文件并安装。 01下载OpenCV4.8源码包 在Ubunt…

解锁楼宇自动化新维度西门子Insight+BACnet IP I/O控制器

数字城市的楼宇自动化已不再是一个遥不可及的概念&#xff0c;而是成为了现代建筑的标配。特别是在大型商业综合体、高端写字楼和公共设施中&#xff0c;高效的楼宇管理系统是确保环境舒适度与能源效率的关键。当提及楼宇自动化领域的佼佼者&#xff0c;西门子Insight楼宇自动化…

KMeans,KNN,Mean-shift算法的学习

1.KMeans算法是什么&#xff1f; 在没有标准标签的情况下&#xff0c;以空间的k个节点为中心进行聚类&#xff0c;对最靠近他们的对象进行归类。 2.KMeans公式&#xff1a; 2. 1.关键分为三个部分&#xff1a; 1.一开始会定义n个中心点&#xff0c;然后计算各数据点与中心点…

jenkins使用gitLab(极狐)认证登陆

jenkins安装 GitLab Authentication插件 我因为java版本和最新GitLab Authentication 1.19版本不兼容&#xff0c;选择了本地安装 找个历史版本1.13版本&#xff0c;然后下载到电脑上 - 本地上传插件并安装 在极狐上创建一个应用 - 配置应用信息 应用名&#xff1a;jenkinsLo…

你以为是个黄金程序员,结果是个王者

玩过王者荣耀的同学都知道&#xff0c;从青铜到王者&#xff0c;过程中需要步步修炼&#xff0c;可以说等级的提升是胜利的积累&#xff0c;也可以说是技术的提升。 而程序员行业也是这样&#xff0c;技术越高段位越高&#xff0c;当然段位越高技术也越高&#xff0c;这个行业大…

Oracle -在线回缩表

conn scott/tiger DROP TABLE EMP1 PURGE; CREATE TABLE EMP1 AS SELECT * FROM EMP; alter table emp1 enable row movement; -- 启动回缩特性 insert into emp1 select * from emp1; / / commit; -- 增加到14000行 -- 分析表的结构 analyze table emp1 comput…

小程序如何确定会员身份并批量设置会员积分或余额

因为一些原因&#xff0c;商家需要从其它系统里面批量导入会员&#xff0c;确定会员身份&#xff0c;然后给他们设置对应的账户余额。下面&#xff0c;就具体介绍如何进行这种操作。 一、客户进入小程序并绑定手机号 进入小程序&#xff1a;客户打开小程序&#xff0c;系统会自…

交友软件源码-源码+搭建+售后,上线即可运营聊天交友源码 专业语聊交友app开发+源码搭建-快速上线

交友小程序源码是一种可以帮助开发者快速搭建交友类小程序的代码模板。它通常包括用户注册、登录、个人信息编辑、匹配推荐、好友聊天等常见功能&#xff0c;以及与后台数据交互的接口。使用这种源码可以极大地缩短开发时间&#xff0c;同时也可以根据自己的需求进行二次开发和…

从0到1开发一个vue3+ts项目(一)

1. 环境配置 1.1 安装node 使用官方安装程序 前往 Node.js 官网&#xff1a;访问 Node.js 官网&#xff0c;下载适合你操作系统的安装程序。运行安装程序&#xff1a;下载完成后&#xff0c;双击安装程序并按照提示进行安装。验证安装&#xff1a;安装完成后&#xff0c;在终…

C++对象的拷贝构造函数

如果一个构造函数的第一个参数是类本身的引用,且没有其它参数(或者其它的参数都有默认值),则该构造函数为拷贝构造函数。 拷贝(复制)构造函数:利用同类对象构造一个新的对象 ●1.函数名和类同名 (构造函数) ●2.没有返回值 (构造函数) ●3.第一个参数必…

软考中级-软件设计师(九)数据库技术基础 考点最精简

一、基本概念 1.1数据库与数据库系统 数据&#xff1a;是数据库中存储的基本对象&#xff0c;是描述事物的符号记录 数据库&#xff08;DataBase&#xff0c;DB&#xff09;&#xff1a;是长期存储在计算机内、有组织、可共享的大量数据集合 数据库系统&#xff08;DataBas…

【C++】模拟实现string

文章目录 前言成员变量成员函数构造函数拷贝构造函数浅拷贝深拷贝拷贝构造函数实现 析构函数赋值重载 空间管理函数元素访问元素修改字符串运算流提取 & 流插入流提取流插入 迭代器begin & endrbegin & rend 总结 前言 模拟实现不是为了写得和库里面一样好。而是为…

OmniReader Pro mac激活版:智慧阅读新选择,开启高效学习之旅

在追求知识的道路上&#xff0c;一款优秀的阅读工具是不可或缺的。OmniReader Pro作为智慧阅读的新选择&#xff0c;以其独特的功能和卓越的性能&#xff0c;为您开启高效学习之旅。 OmniReader Pro具备高效的文本识别和处理技术&#xff0c;能够快速准确地提取文档中的关键信息…

《QT实用小工具·五十四》果冻弹出效果的动画按钮

1、概述 源码放在文章末尾 该项目实现动画按钮&#xff0c;鼠标放在按钮上可以弹性拉出的三个按钮&#xff0c;使用贝塞尔曲线实现&#xff0c;项目demo显示如下所示&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef WATERCIRCLEBUTTON_H #define WATERCIRCLEBUTTON…

Day 43 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

最后一块石头重量Ⅱ 有一堆石头&#xff0c;每块石头的重量都是正整数。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果如下&#xff1a; 如果 x y&#xff0c;那么两…

Transformers中加载预训练模型的过程剖析

使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…