【算法】基础算法001之双指针

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.数组分块(数组划分)

移动零

复写零

2.快慢双指针(循环往复)

快乐数

3.对撞指针->暴力枚举的优化->利用单调性

盛最多水的容器

有效三角形的个数

4.对撞指针->两数之和、三数之和、四数之和

两数之和

三数之和

四数之和


前言

💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐

《算法》专栏正式挂牌成立

  • 《算法》专栏主要是会系统的梳理一些OJ题的算法思想,将他们按照解题方法的不同划分出来,然后归纳总结,当然希望大家多多收藏,以后忘了可以常回来看看!                                    

💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐

本篇文章主要会讲解双指针的思想,双指针是一种非常优秀的算法思想,有对撞指针和快慢指针两种基本用法。

双指针对于有序数据的处理是比较有优势的,当你遇到有序的数据时,你可以尝试着利用双指针或者二分来解题,当然本篇文章只会讲解双指针。

那么双指针思想具体的应用,以及为什么双指针适用于有序数组的处理呢?


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟fanfei_c的仓库🌟

=========================================================================


1.数组分块(数组划分)

数组分块顾名思义,该类题目有一个特性就是将数组中的数据进行分类,然后将分类的数据放在不同的区域上。


移动零

移动零 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/move-zeroes/description/

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

 利用数组分块的思想,我们可以将该数组划分为三个区域:非零的已处理区域、零的已处理区域、待处理区域。

三个区域恰好可以利用两个指针进行分割得到。

所以我们定义两个指针:

  • cur:从左向右扫描数组(遍历数组的作用),主要用来分割已处理区域和待处理区域用;
  • dest:已处理的区域内,非零元素的最后一个位置,主要用来分隔已处理区域内部非零元素和零元素。

得到三个区间:

  • 非零的已处理区域:[0,dest]
  • 零的已处理区域:[dest+1,cur-1]
  • 待处理区域:[cur,n-1]

 有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:void moveZeroes(vector<int>& nums) {for (int dest = -1, cur = 0; cur <= nums.size() - 1; cur++){//如果是零就跳过,不是零进入if (nums[cur]){swap(nums[++dest], nums[cur]);}}}
};

复写零

复写零 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/duplicate-zeros/description/

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

我们可以先尝试着进行异地复写,然后尝试着进行原地复写,看看会发生什么问题?

如果「从前向后」进行原地复写操作的话,由于0的出现会复写两次,导致没有复写的数「被覆
盖掉」。

因此我们选择「从后往前」的复写策略。

但是「从后向前」复写的时候,我们需要找到「最后一个复写的数」,因此我们的大体流程分两
步:

  1. 先找到最后一个复写的数;
  2. 然后从后向前进行复写操作。

 这两步仍然包含一些细节需要处理,比如会不会出现越界问题等?

  • cur:用来遍历数组用。
  • dest:根据cur指向的指进行移动一步或两步,如果dest的位置处于最后一位或者已经越界,跳出循环,如果是越界的情况,我们需要手动将其"拉回",然后进行从后向前的复写操作。

有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:void duplicateZeros(vector<int>& arr) {int dest=-1,cur=0,n=arr.size();//1.先找到cur位置while(cur<n){if(arr[cur])dest++;elsedest+=2;if(dest>=n-1)//这里是为了及时检测是否跳出break;cur++; }//1.5判断dest位置if(dest==n){arr[dest-1]=0;dest-=2;cur--;}//2.然后向前复写while(cur>=0){if(arr[cur])arr[dest--]=arr[cur--]; else{arr[dest--]=0;arr[dest--]=0;cur--;}}}
};

2.快慢双指针(循环往复)

快慢双指针基本思想:使用两个移动速度不同的指针在数组或链表等序列结构上移动。

一般什么情况下适用快慢双指针的题目呢?

这种方法对于处理环形链表或数组非常有用,或者说循环往复的数据都比较适用快慢双指针算法来进行解决。

快乐数

快乐数 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/happy-number/description/

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

 请注意题目意义,只会有两种情况:

  • 情况1:无限循环但始终变不到1
  • 情况2:有限次数内,结果为1

所以对于这种循环往复的数据我们就可以联想到快慢双指针来做:

为了方便理解,我抽象的将数据做成链:

所以必然会成环,slow与fast必然会相遇,我们需要做的就是在他们相遇的时刻,检测以下slow或者fast的值是否为1即可。

有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:int bitSum(int n) {int sum = 0;while (n) {int t = n % 10;sum += t * t;n /= 10;}return sum;}bool isHappy(int n) {int slow = n;int fast = bitSum(n);while (slow != fast) {slow = bitSum(slow);fast = bitSum(bitSum(fast));}return slow == 1;}
};

3.对撞指针->暴力枚举的优化->利用单调性

一般用于顺序结构中,也称左右指针。

对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼
近。

对撞指针的终止条件一般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循
环),也就是:

  • left == right(两个指针指向同⼀个位置)
  • left > right(两个指针错开)

 单调性解题的思路不好想到,但这是一种非常优秀的对暴力枚举方法的优化思想。

盛最多水的容器

盛最多水的容器 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/container-with-most-water/description/

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

 如果说利用暴力枚举的方式来做,很明显你需要固定一边,两层for循环解决,时间复杂度O(N^2),但这道题目作为一道中等难度的题,利用暴力枚举必然会超时。

我们尝试利用对撞指针的方式来做:

w(宽)=right-left;

容积的计算公式:V=h*w

当计算完一组结果之后,我们需要将左指针或右指针向中间移动,这样如此反复就能得到最终答案,可是这样并没有降低时间复杂度,仍然是暴力枚举的思路。

我们观察:

当左指针或右指针向中间移动时w是必然减小的。

又根据木桶原理,h取决于左右指针指向的值小的那一个数据。

本题是依据数据分析,进而得到单调性的关系,需要大家自行画图分析,然后将思路转化成代码。

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

有效三角形的个数

有效三角形的个数 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/valid-triangle-number/description/

 给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

构成三角形的条件:任意两边之和大于第三边

但这个条件转化成代码需要三次判断未免有些麻烦,所以我们可以将数组先进行排序,排序之后如果较小的两个值之和大于第三边,那么就可以构成三角形了。 

暴力枚举的方式很显然时间复杂度O(N^3)。

那我们尝试着对数据进行分析,看看能否利用单调性来优化。

首先排序,我们将最大的数固定,然后利用对撞指针的思想进行优化。

 有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(),nums.end());int n=nums.size();int maxIndex=n-1;int ret=0;while(maxIndex>=2){int left=0;int right=maxIndex-1;while(left<right){if(nums[left]+nums[right]>nums[maxIndex]){  ret+=right-left;right--;}else{left++;}}maxIndex--;}return ret;}
};

4.对撞指针->两数之和、三数之和、四数之和

两数之和

两数之和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

 首先我们发现数组是升序排列的,所以我们想到可以利用双指针来解决,同样的我们利用单调性,看看能否对暴力枚举的策略作优化。

暴力枚举的时间复杂度很明显O(N^2)。

两数之和大于target时,利用单调性,令right--即可;

两数之和小于target时,利用单调性,令left++即可;

两数之和等于target时,我们将此时的结果尾插到结果数组中。

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

三数之和

三数之和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/3sum/description/

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

 本题可以借助两数之和的思想进行解题,无非就是需要多加一层循环,将第三个数固定即可。

另外的两个数仍然为两数之和的思想,只不过此时两数之和等于负的第三个数。

难点:注意本题要求去重,并且要求返回所有满足的数据,所以我们需要处理一些细节问题。

首先,关于返回所有:

  • 当找到一种结果后,不能直接返回,要继续缩小区间继续寻找。

其次,关于去重:

  • 找到一种结果之后,left和right要跳过重复元素。
  • 当使用完一次双指针算法后,即更换第三个数时,也要跳过重复元素。
  • 注意防止越界。

  有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> ret;int n = nums.size();for (int i = 0; i < n;){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) left++;else if (sum > target) right--;else{ret.push_back({ nums[left++],nums[right--],nums[i] });//去重 left 和 rightwhile (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;}}//去重 ii++;while (i < n && nums[i] == nums[i - 1]) i++;}return ret;}
};


四数之和

四数之和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/4sum/description/

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

 四数之和是三数之和的升级,本质上没有任何区别,只不过多加了一个需要固定的数,多加了一层循环而已,如果你已经掌握了三数之和,那么这道题对你来说会非常简单。

 有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(),nums.end());vector<vector<int>> ret;int n=nums.size();for(int i=0;i<n;){for(int j=i+1;j<n;){int left=j+1,right=n-1; long long num=(long long)target-nums[j]-nums[i];//需要注意的细节while(left<right){int sum=nums[left]+nums[right];if(sum>num) right--;else if(sum<num) left++;else{ret.push_back({nums[i],nums[j],nums[left++],nums[right--]});//去重 left 和 rightwhile(left<right && nums[left]==nums[left-1]) left++;while(left<right && nums[right]==nums[right+1]) right--;}}//去重 jj++;while(j<n && nums[j]==nums[j-1]) j++;}//去重ii++;while(i<n && nums[i]==nums[i-1]) i++;}return ret;}
};

以上就是双指针算法在实际题目中的应用,总的来说,双指针算法是比较基础并且简单的算法。

大家只需要记住:当所给数据为有序时,不妨考虑用双指针算法进行解决。


🐸简单总结🐸

双指针擅于处理有序数据,可以解决数组分块、循环往复数据可以利用快慢指针思想(得到某个值可以理解为在某个值处循环)、对撞指针结合单调性可以优化暴力枚举(注意细节:去重和不漏)。


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

NACHI机器人模拟示教器如何切换中文

前言 现在开始学习机器人的编程语言&#xff0c;那么要学习会用首先得用模拟示教器来学习&#xff0c;但是全是英文确实比较难受一些些&#xff0c;没有中文来的直观。所以摸透一下如何给示教器更换语言。 具体步骤 步骤一&#xff1a;将中文的汉化包下载下来。具体的下载链…

JMeter 批量接口测试

一、背景 最近在进行某中台的接口测试准备&#xff0c;发现接口数量非常多&#xff0c;有6、70个&#xff0c;而且每个接口都有大量的参数并且需要进行各种参数验证来测试接口是否能够正确返回响应值。想了几种方案后&#xff0c;决定尝试使用JMeter的csv读取来实现批量的接口…

Window Docker安装

1.下载安装Docker 在Windows上安装Docker桌面_Docker中文网 (dockerdocs.cn)https://dockerdocs.cn/docker-for-windows/install/index.html2.安装完&#xff0c;修改镜像 Docker——Windows版本Docker安装_docker windows-CSDN博客https://blog.csdn.net/weixin_51351637/ar…

【软考中级-软件设计师】day7:图

概述 1-2道选择 图的存储 图的遍历 图的最小生成树 prim算法 kruscal算法 2135476 选7不选6是因为4的先删除&#xff08;vi必须在vj之前&#xff09;跟4有关的删了以后&#xff0c;入度为0的结点只剩下7&#xff0c;所以选7再6

13.若依代码自动生成功能详解

文章目录 1.代码自动生成功能2.功能的使用3. 代码的导出和使用 1.代码自动生成功能 基于若依的目录结构&#xff0c;若依本身提供了代码生成功能&#xff0c;可以根据数据库表的内容&#xff0c;生成一些基本的CRUD的前后端的功能。本文将生成过程中的一些注意事项&#xff0c…

【APACHE】的认识和基础配置参数

#主页传送:江南的江 #每日鸡汤&#xff1a;人生没有如果和假设&#xff0c;只有后果和结果。生活有进有退&#xff0c;输什么也不能输心情。生活简单就是迷人的&#xff0c;学会简单其实就是不简单。要学会平静地接受现实&#xff0c;学会对自己说声顺其自然&#xff0c;学会坦…

如何在OpenWRT部署uhttpd搭建服务器实现远程访问本地web站点

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器&#xff0c;目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器&#xff0c;并且和…

版本控制背景知识

版本控制背景知识 本文是关于 Git 系列文章的导读&#xff0c;我们先介绍一下版本控制的背景知识。 什么是版本控制 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它将什么时候、什么人更改了文件的什么内容等信息如实记录下来…

【2023 - 探索】博0到博1,游戏新地图的探索日志

【2023 - 探索】博0到博1&#xff0c;游戏新地图的探索日志 写在最前面CSDN探索日志2023的探险 探索日志年终回顾探索 冒险回顾实习6月开始跟着老师做科研年中的一些其他事情9月开始上课开学后11月&#xff0c;读者互动 新年展望新年祝福 写在最前面 2023&#xff0c;我解锁了新…

JVM:从零到入门

JVM&#xff0c;就是Java虚拟机。 JVM是一个巨大的话题&#xff0c;我们本文主要简单介绍一些围绕JVM相关的基础知识。 目录 JVM内存区域划分 本地方法栈 虚拟机栈 堆 程序计数器 方法区/ 元数据区 类加载 1.加载 2.验证 3.准备 4.解析 5.初始化 双亲委派模型 …

Kafka消息存储

一、层次结构 具体到某个broker上则是, 数据目录/分区名/日志相关文件集合。其中日志文件集合内包括.log文件, index索引文件和.timeindex时间戳索引文件。 二、.log 结构 .log中记录具体的消息。一般消息由header和body组成, 这点儿在Kafka消息中也同样适用。 message MES…

NSSCTF Interesting_include

开启环境: 通过审计,我们可知: flag在flag.php中,可以利用php中伪协议 payload:?filterphp://filter/readconvert.base64-encode/resourceflag.php 将其base64解码就是flag. NSSCTF{3dc54721-be9e-444c-8228-7133fba76ad4}

【设计模式-02】Strategy策略模式及应用场景

一、参考资料 Java 官方文档 Overview (Java SE 18 & JDK 18)module indexhttps://docs.oracle.com/en/java/javase/18/docs/api/index.html Java中使用到的策略模式 Comparator、comparable Comparator (Java SE 18 & JDK 18)declaration: module: java.base, pa…

RHCE9学习指南 第20章 计划任务

有时需要在某个指定的时间执行一个操作&#xff0c;此时就要使用计划任务了。计划任务有两种&#xff1a;一个是at计划任务&#xff0c;另一个是crontab计划任务。 下面我们分别来看这两种计划任务的使用。 20.1 at at计划任务是一次性的&#xff0c;到了指定的时间点时就开始…

关于jointjs的详细介绍

1. 介绍 1.1 JointJS简介 JointJS是一个专注于图形可视化的js库&#xff0c;用于创建交互式的图形和图表。它基于HTML、SVG&#xff08;可缩放矢量图形&#xff09;和CSS&#xff08;层叠样式表&#xff09;技术&#xff0c;主要用于在Web应用程序中实现可视化图形编辑器或图表…

USB-XM2403 USB接口的温度数据采集卡

USB2.0总线24位测温卡&#xff0c;支持1路热电阻模式、6路热电偶模式&#xff0c;无需外接调理。同时支持多量程电压测量。16路双向IO&#xff0c;2路计数/测频&#xff0c;3路PWM输出。 功能简介 USB-XM2403是一款USB接口的温度数据采集卡。USB-XM2403具有7路模拟输入&#xf…

糊涂(Hutool)工具常见的使用方法

简介&#xff1a; Hutool是一个小而全的Java工具类库&#xff0c;通过静态方法封装&#xff0c;降低相关API的学习成本&#xff0c;提高工作效率&#xff0c;使Java拥有函数式语言般的优雅&#xff0c;让Java语言也可以“甜甜的”。 安装&#xff1a; 在我们的maven中引入糊涂…

vue3+echarts应用——深度遍历html的dom结构并用树图进行可视化

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐html数据解析&#x1f496; html字符串转为html对象&#x1f496; 深度遍历html对象内容 ⭐echarts 树图的渲染&#x1f496; 处理html内容为树状结构&#x1f496; 渲染树状图&#x1f496; inscode代码块 ⭐总结⭐结束 ⭐前言 大…

安全漏洞周报(2024.01.01-2023.01.08)

漏洞速览 ■ 用友CRM系统存在逻辑漏洞 漏洞详情 1. 用友CRM系统存在逻辑漏洞 漏洞介绍&#xff1a; 某友CRM系统是一款综合性的客户关系管理软件&#xff0c;旨在帮助企业建立和维护与客户之间的良好关系。它提供了全面的功能&#xff0c;包括销售管理、市场营销、客户服…

vagrant 用户名密码登录

正常登录后 sudo -i 切换到root权限 vim /etc/ssh/vim sshd_config 将PasswordAuthentication no设置 为yes 重启sshd.service服务 systemctl restart sshd.service