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

news2025/1/9 20:09:26

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.coloradmin.cn/o/1649425.html

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

相关文章

mvc 异步请求、异步连接、异步表单

》》》 利用Jquery ajax 》》》 mvc 异步表单 c# MVC 添加异步 jquery.unobtrusive-ajax.min.js 方法 具–>Nuget程序包管理器–>程序包管理器控制台 在控制台输入&#xff1a;PM>Install-Package Microsoft.jQuery.Unobtrusive.Ajax –version 3.0.0 回车执行即可在…

【Diffusion实战】训练一个类别引导diffusion模型(Pytorch代码详解)

又学习了一种方法&#xff0c;类别引导diffusion模型&#xff0c;使用mnist数据集&#xff0c;记录一下它的用法吧。 Diffusion实战篇&#xff1a;   【Diffusion实战】训练一个diffusion模型生成S曲线&#xff08;Pytorch代码详解&#xff09;   【Diffusion实战】训练一个…

如何处理多模态数据噪声不均衡动态?天大等最新《低质量数据的多模态融合》综述

多模态融合致力于整合来自多种模态的信息&#xff0c;目的是实现更准确的预测。在包括自动驾驶和医疗诊断等广泛的场景中&#xff0c;多模态融合已取得显著进展。然而&#xff0c;在低质量数据环境下&#xff0c;多模态融合的可靠性大部分仍未被探索。本文综述了开放多模态融合…

Linux专栏03:使用Xshell远程连接云服务器

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Linux专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 使用Xshell远程连接云服务器 编号&#xff1a;03 文章目录 使用Xsh…

鸿蒙OpenHarmony实战开发-MiniCanvas

介绍 基于OpenHarmony的Cavas组件封装了一版极简操作的MiniCanvas&#xff0c;屏蔽了原有Canvas内部复杂的调用流程&#xff0c;支持一个API就可以实现相应的绘制能力&#xff0c;该库还在继续完善中&#xff0c;也欢迎PR。 使用说明 1.添加MiniCanvas依赖 在项目entry目录…

Spring Boot3.x集成Disruptor4.0

Disruptor介绍 Disruptor是一个高性能内存队列&#xff0c;研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单&#xff0c;2010年在QCon演讲后&#xff0c;获得了业界关注。2011年&…

C++手写协程项目(协程实现线程结构体、线程调度器定义,线程挂起函数、线程切换函数、线程恢复函数、线程结束函数、线程结束判断函数,模块测试)

协程结构体定义 之前我们使用linux下协程函数实现了线程切换&#xff0c;使用的是ucontext_t结构体&#xff0c;和基于这个结构体的四个函数。现在我们要用这些工具来实现我们自己的一个线程结构体&#xff0c;并实现线程调度和线程切换、挂起。 首先我们来实现以下线程结构体…

【iOS】——浅析CALayer

文章目录 一、CALayer介绍二、UIview与CALayer1.区别2.联系 三、CALayer的使用1.初始化方法2.常用属性 四.CALayer坐标系1.position属性和anchorPoint属性2.position和anchorPoint的关系3.position、anchorPoint和frame的关系 五、CALayerDelegate六、CALayer绘图机制1.绘图流程…

官方教程来啦!上手体验YashanDB主备部署、同步延迟和自动切换能力

在上一篇深度干货 | 如何兼顾性能与可靠性&#xff1f;一文解析YashanDB主备高可用技术中&#xff0c;我们深入探讨了YashanDB高可用的架构设计原理和关键技术&#xff0c;本文将聚焦于实践操作&#xff0c;快速体验YashanDB的主备高可用能力。 概要 YashanDB提供了不同部署形…

C++程序设计教案

文章目录&#xff1a; 一&#xff1a;软件安装环境 第一种&#xff1a;vc2012 第二种&#xff1a;Dev-C 第三种&#xff1a;小熊猫C 二&#xff1a;语法基础 1.相关 1.1 注释 1.2 换行符 1.3 规范 1.4 关键字 1.5 ASCll码表 1.6 转义字符 2.基本框架 2.1 第一种&…

如果insightface/instantID安装失败怎么办(关于InsightFaceLoader_Zho节点的报错)

可能性有很多&#xff0c;但是今天帮朋友解决问题的时候又收集了一种新的思路。 首先&#xff0c;可以先按照这篇文章里边提到的方法去安装&#xff1a; 【全网最详细】ComfyUI下&#xff0c;Insightface安装指南-聚梦小课堂_insightface如何安装-CSDN博客 其次&#xff0c;…

解决Python中的 `ModuleNotFoundError: No module named ‘fcmeans‘` 错误

ModuleNotFoundError: No module named fcmeans 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误如何解决这个错误fcmeans 库简介应用实例 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误 在进行数据科学或机器学习项目时&#xff0c;…

Linux内核之获取文件系统超级块:sget用法实例(六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

大眼橙C1 Air投影仪:千元预算内的明智之选

在科技日新月异的今天&#xff0c;投影仪已经不再是会议室或教室的专属&#xff0c;而是越来越多地走入了寻常百姓家。家庭影院的概念越来越流行&#xff0c;尤其在都市人之间逐渐成为一股风尚。市场上投影仪非常多&#xff0c;如何选到一台合适的投影仪也成为困扰广大用户的一…

了解TMS运输管理系统,实现物流高效运转

TMS运输管理系统&#xff08;Transportation Management System&#xff09;是一种集成物流和信息技术的解决方案&#xff0c;通过优化运输流程、实时跟踪货物信息和自动化管理操作&#xff0c;提高物流效率&#xff0c;降低运营成本&#xff0c;实现高效运输。 TMS运输管理系…

软件设计师-重点的构造型设计模式

一、桥接模式&#xff08;Bridge&#xff09;&#xff1a; 意图&#xff1a; 将抽象部分与其实现部分分离&#xff0c;使它们都可以独立地变化。 结构&#xff1a; 适用性&#xff1a; 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如&#xff0c;这种情况可能是…

探索大模型能力--prompt工程

1 prompt工程是什么 1.1 什么是Prompt&#xff1f; LLM大语言模型终究也只是一个工具&#xff0c;我们不可能每个人都去训一个大模型&#xff0c;但是我们可以思考如何利用好大模型&#xff0c;让他提升我们的工作效率。就像计算器工具一样&#xff0c;要你算10的10倍&#x…

【计算机网络】计算机网络的性能指标

计算机网络的性能指标被用来从不同方面度量计算机网络的性能。常用的八个计算机网络性能指标&#xff1a;速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率、丢包率。 一.速率 (1) 数据量 比特&#xff08;bit&#xff0c;记为小写b&#xff09;是计算机中数据量的基…

JavaWEB 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…

c++ 线程交叉场景试验

1.需求 1.处理一个列表的数据&#xff0c;要求按照列表的数据处理10个数据 2.可以使用多线程处理&#xff0c;但是针对每个线程&#xff0c;1~10的处理顺序不能变。 3.每个数据的处理必须原子&#xff0c;即只有一个线程可以针对某个数据进行处理&#xff0c;但是10个数据是可…