【算法】--双指针

news2024/11/15 11:40:33

文章目录

  • 双指针
  • 例题
    • 移动零
    • 复写零
    • 快乐数
    • 盛⽔最多的容器
    • 有效三角形的个数
    • 和为 s 的两个数字
    • 三数之和
    • 四数之和

双指针

常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是快慢指针。

对撞指针:⼀般⽤于顺序结构中,也称左右指针。

对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
◦ left == right (两个指针指向同⼀个位置)
◦ left > right (两个指针错开)

快慢指针:又称为龟兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。

这种方法对于处理环形链表或数组非常有用。
其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。
快慢指针的实现⽅式有很多种,最常⽤的⼀种就是:
• 在⼀次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现⼀快⼀慢。

例题

移动零

移动零

在这里插入图片描述
思路:
在本题中,我们可以⽤⼀个 cur 指针来扫描整个数组,另⼀个 dest 指针⽤来记录⾮零数序列的最后⼀个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。在 cur 遍历期间,使 [0, dest] 的元素全部都是⾮零元素, [dest + 1, cur - 1] 的元素全是零。

算法流程:
a. 初始化 cur = 0 (⽤来遍历数组), dest = -1 (指向⾮零元素序列的最后⼀个位置。
因为刚开始我们不知道最后⼀个⾮零元素在什么位置,因此初始化为 -1 )
b. cur 依次往后遍历每个元素,遍历到的元素会有下⾯两种情况:

  1. 遇到的元素是 0 , cur 直接 ++ 。因为我们的⽬标是让 [dest + 1, cur - 1] 内 的元素全都是零,因此当 cur 遇到 0 的时候,直接 ++ ,就可以让 0 在 cur - 1 的位置上,从⽽在 [dest + 1, cur - 1] 内;

  2. 遇到的元素不是 0 , dest++ ,并且交换 cur 位置和 dest 位置的元素,之后让 cur++ ,扫描下⼀个元素。

    • 因为 dest 指向的位置是⾮零元素区间的最后⼀个位置,如果扫描到⼀个新的⾮零元 素,那么它的位置应该在 dest + 1的位置上,因此 dest 先⾃增 1 ;
    • dest++ 之后,指向的元素就是 0 元素(因为⾮零元素区间末尾的后⼀个元素就是 0),因此可以交换到 cur 所处的位置上,实现 [0, dest] 的元素全部都是⾮零 元素, [dest + 1, cur - 1]的元素全是零。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
for(int dest=-1,cur=0;cur<nums.size();cur++)
{
    if(nums[cur])swap(nums[++dest],nums[cur]);
}
return ;
    }
};

复写零

复写零

在这里插入图片描述
算法思路:
如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两步:
1.先找到最后⼀个复写的数;
2.然后从后向前进行复写操作。

class Solution
{
public:
 void duplicateZeros(vector<int>& arr) 
 {
 // 1. 先找到最后⼀个数
 int cur = 0, dest = -1, n = arr.size();
 while(cur < n)
 {
 if(arr[cur]) dest++;
 else dest += 2;
 if(dest >= n - 1) break;
 cur++;
 }
 // 2. 处理⼀下边界情况
 if(dest == n)
 {
 arr[n - 1] = 0;
 cur--; dest -=2;
 }
 // 3. 从后向前完成复写操作
 while(cur >= 0)
 {
 if(arr[cur]) arr[dest--] = arr[cur--];
 else
 {
 arr[dest--] = 0;
 arr[dest--] = 0;
 cur--;
 }
 }
 }
}

快乐数

快乐数
在这里插入图片描述
「快乐数」 定义为:
对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和。
然后重复这个过程直到这个数变为 1,也可能是⽆限循环但始终变不到 1 。
如果这个过程 结果为 1 ,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
在这里插入图片描述
可以看到,如果不是快乐数,最后会出现没有1 的循环,如果是快乐数,最后会出现1的循环

因此,变化的过程最终会⾛到⼀个圈⾥⾯,因此可以⽤「快慢指针」来解决。

算法思路:
根据上述的题⽬分析,我们可以知道,当重复执⾏ x 的时候,数据会陷⼊到⼀个「循环」之中。⽽「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的,也就是说他们总会相遇在⼀个位置上。如果相遇位置的值是 1 ,那么这个数⼀定是快乐数;如果相遇位置不是 1的话,那么就不是快乐数。

class Solution
{
public:
 int bitSum(int n) // 返回 n 这个数每⼀位上的平⽅和{
 int sum = 0;
 while(n)
 {
 int t = n % 10;
 sum += t * t;
 n /= 10;
 }
 return sum;
 }
 bool isHappy(int n) 
 {
 int slow = n, fast = bitSum(n);
 while(slow != fast)
 {
 slow = bitSum(slow);
 fast = bitSum(bitSum(fast));
 }
 return slow == 1;
 }
};

盛⽔最多的容器

盛⽔最多的容器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对撞指针
算法思路:
设两个指针 left , right 分别指向容器的左右两个端点,此时容器的容积 :v = (right - left) * min( height[right], height[left]) 容器的左边界为 height[left] ,右边界为 height[right] 。
为了方便叙述,我们假设「左边边界」小于「右边边界」。
如果此时我们固定⼀个边界,改变另⼀个边界,水的容积会有如下变化形式:
◦ 容器的宽度一定变小。
◦ 由于左边界较小,决定了水面高度。如果改变左边界,新的水面高度不确定,但是⼀定不会超
过右边的柱子高度,因此容器的容积可能会增大。
◦ 如果改变右边界,无论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会
超过现在的水面高度,但是由于容器的宽度减小,因此容器的容积⼀定会变小的。
由此可见,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继续去判断下⼀个左右边界。

当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left 与 right 相
遇。期间产生的所有的容积里面的最大值,就是最终答案。

class Solution
{
public:
 int maxArea(vector<int>& height) 
 {
 int left = 0, right = height.size() - 1, ret = 0;
 while(left < right)
 {
 int v = min(height[left], height[right]) * (right - left);
 ret = max(ret, v);
 // 移动指针
 if(height[left] < height[right]) left++;
 else right--;
 }
 return ret;
 }
};

有效三角形的个数

有效三角形的个数
在这里插入图片描述

算法思路:
先将数组排序。
我们可以固定⼀个「最长边」,然后在比这条边小的有序数组中找
出⼀个二元组,使这个二元组之和大于这个最长边。由于数组是有序的,我们可以利用「对撞指针」来优化。

设最长边枚举到 i 位置,区间 [left, right] 是 i 位置左边的区间(也就是比它小的区间):
◦ 如果 nums[left] + nums[right] > nums[i] :
▪ 说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成比nums[i] ⼤的⼆元组
▪ 满足条件的有 right - left 种
▪ 此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进⼊下⼀轮判断
◦ 如果 nums[left] + nums[right] <= nums[i] :
▪ 说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满足条件的⼆元组
▪ left 位置的元素可以舍去, left++ 进⼊下轮循环

class Solution
{
public:
 int triangleNumber(vector<int>& nums) 
 {
 // 1. 优化
 sort(nums.begin(), nums.end());
 // 2. 利⽤双指针解决问题
 int ret = 0, n = nums.size();
 for(int i = n - 1; i >= 2; i--) // 先固定最⼤的数
 {
 // 利⽤双指针快速统计符合要求的三元组的个数
 int left = 0, right = i - 1;
 while(left < right)
 {
 if(nums[left] + nums[right] > nums[i])
 {
 ret += right - left;
 right--;
 }
 else
 {
 left++;
 }
 }
 }
 return ret;
 }
};

和为 s 的两个数字

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

在这里插入图片描述
由于数组是升序的,我们可以用对撞指针,l,r指针分别从左右往中间走,如果和大于目标值,r指针向左走,小于则l指针向右走,等于时直接返回,当指针对撞时如果还没返回则退出循环。

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
int l=0,r=price.size()-1;
while(l<r)
{
    int sum=price[l]+price[r];
    if(sum>target)r--;
    else if(sum<target)l++;
    else return {price[l],price[r]};
}
return {-1,-1};
    }
};

三数之和

在这里插入图片描述
和上面两数之和类似,这里直接让两树和等于目标数+第三数的相反数即可。
由于这里的数组不是有序的,可以先排一下序再用对撞指针解决问题。但是要注意的是,这道题里面需要有「去重」操作

class Solution
{
public:
 vector<vector<int>> threeSum(vector<int>& nums) 
 {
 vector<vector<int>> ret;
 // 1. 排序
 sort(nums.begin(), nums.end());
 // 2. 利⽤双指针解决问题
 int n = nums.size();
 for(int i = 0; i < n; ) // 固定数 a
 {
 if(nums[i] > 0) break; // ⼩优化
 int left = i + 1, right = n - 1, target = -nums[i];
 while(left < right)
 {
 int sum = nums[left] + nums[right];
 if(sum > target) right--;
 else if(sum < target) left++;
 else
 {
 ret.push_back({nums[i], nums[left], nums[right]});
 left++, right--;
 // 去重操作 left 和 right
 while(left < right && nums[left] == nums[left - 1]) left++;
 while(left < right && nums[right] == nums[right + 1]) 
right--;
}
 }
 // 去重 i 
 i++;
 while(i < n && nums[i] == nums[i - 1]) i++;
 }
 return ret;
 }
};

四数之和

四数之和
在这里插入图片描述
与上题类似,固定一个数,转到三数之和,注意去重。

class Solution
{
public:
 vector<vector<int>> fourSum(vector<int>& nums, int target) 
 {
 vector<vector<int>> ret;
 // 1. 排序
 sort(nums.begin(), nums.end());
 // 2. 利⽤双指针解决问题
 int n = nums.size();
 for(int i = 0; i < n; ) // 固定数 a
 {
 // 利⽤ 三数之和
 for(int j = i + 1; j < n; ) // 固定数 b
 {
 // 双指针
 int left = j + 1, right = n - 1;
 long long aim = (long long)target - nums[i] - nums[j];
 while(left < right)
 {
 int sum = nums[left] + nums[right];
 if(sum < aim) left++;
 else if(sum > aim) right--;
 else
 {
 ret.push_back({nums[i], nums[j], nums[left++], 
nums[right--]});
 // 去重⼀
 while(left < right && nums[left] == nums[left - 1]) 
left++;
 while(left < right && nums[right] == nums[right + 1]) 
right--;
 }
 }
 // 去重⼆
 j++;
 while(j < n && nums[j] == nums[j - 1]) j++;
 }
 // 去重三
 i++;
 while(i < n && nums[i] == nums[i - 1]) i++;
 }
 return ret;
 }
};

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

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

相关文章

python判断语句(三)

下一篇持续更新中… 如有错误欢迎指正&#xff01;&#xff01;&#xff01; 文章目录 前言学习内容 布尔类型和比较运算符布尔类型比较运算符总结 if语句的基本格式if语句的注意点总结案例 if else语句注意点总结案例 if elif else语句注意点总结案例 判断语句的嵌套语法格式…

哪里能够使用Claude-100k?

Claude-100k 是 Anthropic 开发的一个大型语言模型&#xff0c;可以在以下几种方式中使用&#xff1a; Anthropic 官网&#xff1a;可以通过访问 Anthropic 的官方网站来获取 Claude-100k 的使用许可或API访问权限。 API 服务&#xff1a;Claude-100k 也可以通过 API 服务进行…

fastchat与autogen使用要点澄清

说明&#xff1a; 本文重点是想使用autogen构建智能体&#xff0c;并且想要通过加载本地模型来构建&#xff0c;以灵活使用。但是autogen重点是以API调用支持openai, mistral等大模型使用的&#xff0c;对于使用国内的一些模型不是那么友好方便。然后在查找方法的过程中&#x…

ElasticSearch学习笔记(五)Bucket聚合、Metric聚合

文章目录 前言9 项目实战9.3 我周边的酒店9.4 酒店竞价排名 10 数据聚合10.1 聚合的分类10.2 DSL实现聚合10.2.1 Bucket聚合10.2.2 聚合结果排序10.2.3 限定聚合范围10.2.4 Metric聚合 10.3 RestAPI实现聚合10.3.1 API语法10.3.2 业务需求10.3.3 业务实现10.3.4 功能测试 前言 …

人工智能、机器学习和深度学习有什么区别?应用领域有哪些?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…

RLHF(带有人类反馈的强化学习)初探

我的目标是&#xff0c;在决策游戏上学习某人风格&#xff0c;可以让人对战“带有某人风格”的AI&#xff0c;比如你可以在这里对战“sky风格的AI”,这样的效果。 我最开始受到的启发来源于xbox的广告《爸爸的幽灵车》&#xff0c;已故人在游戏中留下的速度记录的固定轨迹。 …

传统CV算法——仿射变换原理及应用

可以理解一下常规的翻转和平移。“线性变换”“平移”空间变换中的仿射变换对应着五种变换&#xff0c;平移&#xff0c;缩放&#xff0c;旋转&#xff0c;翻转&#xff0c;错切。而这五种变化由原图像转变到变换图像的过程&#xff0c;可以用仿射变换矩阵进行描述。而这个变换…

异地多活架构计算设计

随着互联网的飞速发展,企业对业务连续性和高可用性的需求日益增加。异地多活架构作为一种高可靠性的系统设计方案,通过在地理上分散的多个数据中心部署应用和数据,有效降低了单一故障点对整个系统的影响,确保业务在灾难发生时能够持续运行。 架构设计策略 业务解耦:将系…

Servelet学习-24.9.3

文章目录 前言一、Servelet概述1.1 简单入门&#xff1a;2.2 生命周期 二、HttpServletRequest对象2.1 常用方法 三、HttpServeletResponse对象 前言 九月&#xff0c;加油 一、Servelet概述 Servelet&#xff1a; server applet servelet就是一个接口,定义了Java类被浏览器访…

《大道平渊》· 廿壹 —— 杀心篇:何谓 “杀心”?本质上,就是寻求杀心的一个过程。

《大道平渊》 "行有不得&#xff0c;反求诸己。" ——《论语 学而》 指的是遇事遭困&#xff0c;须在自身寻因&#xff0c;而非怨天尤人&#xff0c;一味地归咎于外因。 凡事向内求也&#xff0c;多多自省&#xff0c;提高自身的修养和能力&#xff0c;取得成功。…

基于yolov8的106种鲜花识别花朵检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的106种鲜花识别花朵检测系统是一项融合了先进深度学习技术的创新应用。该系统利用YOLOv8算法&#xff0c;这一目前最先进的目标检测模型之一&#xff0c;实现了对106种不同花卉的快速、准确识别。 YOLOv8以其速度快、准确性高和鲁棒性强的特点&#…

模拟算法专题——算法介绍算法讲解力扣实战应用

目录 1、模拟算法介绍 2、算法应用【leetcode】 2.1 替换所有的问号 2.1.1 算法思想 2.1.2 算法代码 2.2 提莫攻击 2.2.1 算法思想 2.2.2 算法代码 2.3 Z字形变换 2.3.1 算法思想 2.3.2 算法代码 2.4 外观数列 2.4.1 算法思想 2.4.2 算法代码 2.5 数青蛙 2.5.1 算…

复旦NLP团队新作:大规模语言模型从理论到实践PDF版

2022 年 11 月&#xff0c;Chat GPT 的问世展示了大语言模型的强大潜能&#xff0c;并迅速引起了广泛关注。Chat GPT 能够有效理解用户需求&#xff0c;并根据上下文提供恰当的回答。它不仅可以进行日常对话&#xff0c;还能够完成复杂任务&#xff0c;如撰写文章、回答问题等。…

测试工程师学历路径:从功能测试到测试开发

现在软件从业者越来越多&#xff0c;测试工程师的职位也几近饱和&#xff0c;想要获得竞争力还是要保持持续学习。基本学习路径可以从功能测试-自动化测试-测试开发工程师的路子来走。 功能测试工程师&#xff1a; 1、软件测试基本概念&#xff1a; 学习软件测试的定义、目的…

Cubase里如何使用效果器插件?

Cubase里如何使用效果器插件&#xff1f;具体操作步骤如下&#xff1a; 1、首先&#xff0c;在你的电脑上打开Cubase软件。进入页面后&#xff0c;单击菜单栏上的设备以进入插件管理器&#xff0c;如下所示&#xff0c;然后继续下一步。 2、接下来&#xff0c;弹出插件管理器窗…

银行业智能化转型:智能客服的崛起与挑战

更多内容个人网站&#xff1a;孔乙己大叔 在当今这个科技日新月异的时代&#xff0c;银行业作为传统金融业的支柱&#xff0c;正经历着一场前所未有的变革。智能客服的兴起&#xff0c;不仅重塑了银行的服务模式&#xff0c;也深刻影响着银行员工的职业生涯。这场由技术驱动的变…

遥控器显示分别对应的无人机状态详解!!

1. 电量显示 遥控器电量&#xff1a;遥控器上通常会显示自身的电池电量&#xff0c;以提醒用户及时充电。 无人机电量&#xff1a;部分高端遥控器还会显示无人机的电池电量&#xff0c;以进度条或百分比的形式表示&#xff0c;帮助用户了解无人机的续航能力。 2. 飞行模式与…

24并发设计模式——线程池模式

一、线程池模式介绍 线程池模式&#xff08;Thread Pool Pattern&#xff09;是一种并发设计模式&#xff0c;用于管理和循环使用线程资源以处理大量任务。它旨在提高系统性能和资源利用率&#xff0c;特别是在需要频繁创建和销毁线程的环境中。 1、线程池模式结构图 线程池管…

弱通联条件下的人机混合控制

弱通联条件下的人机混合控制指的是在通信连接不稳定或不可靠的情况下&#xff0c;如何有效地将人工控制与自动化/智能化系统结合起来进行操作。这种情况下&#xff0c;控制系统需要设计得既能在网络问题时维持基本功能&#xff0c;又能充分利用人工输入来补充自动系统的不足。下…

Win10提示输入网络凭据解决方法(Win10 Prompts for Entering Network Credentials Solution)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…