《前端算法宝典:双指针问题解析与应用》

双指针

双指针,指的是在遍历对象的过程中使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针或者是两个指针构成一个滑动窗口进行扫描,从而达到相应的目的。

双指针方法在某些情况下可以对有序数组的运算做一些优化。

接雨水

题目如图所示

在这里插入图片描述

解题思路

利用双指针从数组两侧向中间遍历,过程中分别计算左指针及其左侧最高高度和右指针及其右侧最高高度。找到对应的最高高度较低的那一列直接更新结果。

code

    window.onload = function () {var trap = function () {//定义数组长度let height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1];let n = height.length;//定义左指针及其左侧最高,右指针及其右侧最高let left = 0;//左指针指向最左边的位置let leftMax = 0;let right = n - 1;//定义右指针指向数组末尾的位置let rightMax = 0;let res = 0;//定义变量记录结果while (left < right) {//找出数组两侧的最大值leftMax = Math.max(leftMax, height[left]);rightMax = Math.max(rightMax, height[right]);// console.log("数组左右两侧的最大值" + leftMax, rightMax)// 存水量取决于短板,数组两次最大值中较小的一个用指针遍历if (leftMax < rightMax) {res += leftMax - height[left];left++;// console.log("数组左侧最大值较小的时候的res" + res)} else {res += rightMax - height[right];right--;// console.log("数组右侧最大值较小的时候的res" + res)}}return res;};trap();}

代码中的console.log是为了更直观的看出数组两侧最大值和res的变化过程。

下面附上控制台的输出截图

在这里插入图片描述

对撞指针

对撞指针是指在数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。

对撞数组适用于连续数组和字符串,也就是说当你遇到题目给定连续数组和字符串时,应该第一时间想到用对撞指针解题。

验证回文串

题目如图所示

在这里插入图片描述

解题思路

先都转成大写(不然会出现 a A 判定为不相同)
设置头尾双指针,开启循环
如果指向的元素是不是有效的(不是字母和数字),则跳过
如果指向的元素有效,但不相同,则不是回文,返回false
否则有效,且相同,收缩指针,继续循环
直至指针相遇,循环结束,始终没有返回false,返回true。

code

/*** @param {string} s* @return {boolean}*/
var isPalindrome = function (s) {// 将字符都转换为大写,避免大小写不同而误判s = s.toUpperCase();// 设置头尾双指针let left = 0;let right = s.length - 1;while (left < right) {// 如果左指针指向元素不是字母或者数字,跳过(左指针右移直接下一个)if (!isValid(s[left])) {left++;continue;}// 如果右指针指向元素不是字母或者数字,跳过(右指针左移直接下一个)if (!isValid(s[right])) {right--;continue;}// 如果左右指针指向字母和数字,但不相同,不是回文返回false;if (s[left] != s[right]) {return false;}left++;right--;}return true;
};

盛最多水的容器

题目如图所示

在这里插入图片描述

解题思路

定义双指针left,right对应数组的两侧,循环遍历height数组,left和right对应的高度较小的先向内移动,不断计算面积更新最大面积

复杂度:时间复杂度O(n),n是数组height的长度,遍历一次。空间复杂度O(1)

code

/*** @param {number[]} height* @return {number}*/
var maxArea = function (height) {let maxArea = 0; // 用于存储最大面积let left = 0; // 左边界指针let right = height.length - 1; // 右边界指针// 当左指针小于右指针时循环while (left < right) {const minHeight = Math.min(height[left], height[right]); // 计算左右指针所指高度的最小值const width = right - left; // 计算当前宽度const area = minHeight * width; // 计算当前面积maxArea = Math.max(maxArea, area); // 更新最大面积// 移动指针,让高度较小的一侧向内移动,以寻找可能更大的面积if (height[left] < height[right]) {left++;} else {right--;}}return maxArea; // 返回最大面积
};

两数之和 II - 输入有序数组

题目如图所示

在这里插入图片描述

解题思路

定义左右指针位于数组两侧,左指针指向数组起始位,右指针指向末尾位。
计算当前左右指针对应值的和(两数之和)是否大于或者小于还是等于目标值。

因为数组是有序递增的,所以若两数之和大于目标值,应该右指针左移减小两数之和使其逼近目标值;

若两数之和小于目标值则需要减小和来逼近目标值,左指针右移,当两数之和等于目标值的时候,返回左右指针的索引,切记本题题目索引值是从1开始的,所以需要
返回的索引值加一。

在这里插入图片描述

code

/*** @param {number[]} numbers* @param {number} target* @return {number[]}*/
var twoSum = function (numbers, target) {// 定义变量n获取数组的长度let n = numbers.length;// 定义左右指针let left = 0;let right = n - 1;while (left < right) {//比较当前数组两端最大值和最小值之和与目标值的大小//若当前数组两侧之和大于目标值右指针左移if (numbers[left] + numbers[right] > target) {right--;//若当前数组两侧之和小于目标值左指针右移} else if (numbers[left] + numbers[right] < target) {left++;// 若等于目标值则返回左右指针的索引(注意题目的索引是从1开始的,而代码是从0开始的,所以返回的索引值需要加一)} else {return [left + 1, right + 1]}}return [left + 1, right + 1];
};

三数之和

题目如图所示

在这里插入图片描述

解题思路

标签:数组遍历
首先对数组进行排序,排序后固定一个数 nums[i]nums[i]nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sum 判断是否满足为 0,满足则添加进结果集
如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环
如果nums[i] == nums[i−1]nums,则说明该数字重复,会导致结果重复,所以应该跳过
当 sum == 0 时,nums[L] == nums[L+1]则会导致结果重复,应该跳过,L++
当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
时间复杂度:O(n^2) n 为数组长度

code

/*** @param {number[]} nums* @return {number[][]}*/
//  碰撞指针解题
var threeSum = function (nums) {let n = nums.length;//定义n为数组长度let ans = [] //记录结果的数组// 首先对数组进行排序nums.sort((a, b) => a - b);// 遍历数组for (let i = 0; i < n; i++) {// 若nums[i]大于零,那么三数之和一定大于零if (nums[i] > 0) break;// 如果当前数字与上一个数字一样,直接跳过// (注意数组越界问题)if (i > 0 && nums[i] == nums[i - 1]) continue;let left = i + 1;let right = n - 1;while (left < right) {//计算三数之和let s = nums[i] + nums[left] + nums[right]if (s == 0) {ans.push([nums[i],nums[left],nums[right]]);//指针当前指向和刚才的一样就直接跳过// 左指针所指当前数字和前一个数字一样,直接跳过去重while (left < right && nums[left] == nums[left + 1]) {left++;}while (left < right && nums[right] == nums[right - 1]) {right--;}left++;right--;} else if (s < 0) {left++;} else if (s > 0) {right--;}}}return ans
};

滑动窗口

滑动窗口实际是两个指针之间形成的区域,下面是滑动窗口的两个指针的移动方式

  1. 初始时,左右指针left,right都指向第0个元素,窗口为[left,right),注意这里是左闭右开,因此初始窗口[0,0)区间没有元素,符合我们的初始定义
  2. 开始循环遍历整个数组元素,判断当前right指针是否超过整个数组的长度,是退出循环,否则执行第3步
  3. 然后right指针开始向右移动一个长度,并更新窗口内的区间数据
  4. 当窗口区间的数据满足我们的要求时,右指针right就保持不变,左指针left开始移动,直到移动到一个不再满足要求的区间时,left不再移动位置
  5. 执行第2步

无重复字符的最长子串

题目如图

在这里插入图片描述

解题思路

在字符串s中定义两个指针,左指针指向数组首位索引,右指针指向数组第二索引

用一个临时数组temp保存[left,right)的字符(包含左指针不包含右指针);

若temp集合中包含s中right所指元素,左指针右移,不包含右指针右移

定义变量max用来存储最长子串。

右指针索引-左指针索引大于max的时候,将右指针索引-左指针索引赋给max

在while循环外返回max;

code

var lengthOfLongestSubstring = function (s) {if (s.length <= 1) {return s.length;}let left = 0;let right = 1;let max = 0;let temp;//    指针右移过程while (right < s.length) {//temp保存s中[left,right)的元素temp = s.slice(left, right);//若temp集合中包含s中right所指元素,左指针右移if (temp.indexOf(s.charAt(right)) > -1) {left++;continue;//跳出当前循环中之后的代码,进入下一次循环} else {right++;}if (right - left > max) {max = right - left;}}return max;
};

找到字符串中所有字母异位词

题目如图所示
在这里插入图片描述

解题思路

首先定义两个空对象 need 和 window,分别用来存储字符串 t 中每个字符的出现次数以及当前窗口中每个字符的出现次数。

遍历字符串 t,统计每个字符出现的次数,并保存在 need 对象中。

使用两个指针 left 和 right 分别表示窗口的左、右边界。另外定义了 valid 表示窗口中与 t 中字符匹配的数量,res 用来存储匹配成功的起始索引。

通过一个 while 循环,不断右移 right 指针,将新字符加入窗口。如果新字符在 need 中,则更新 window 中相应字符的出现次数,并检查是否与 need 中对应字符的出现次数匹配。

另外,在判断窗口大小超过 t 的长度时,通过一个内层的 while 循环,不断左移 left 指针,并更新窗口中字符的出现次数。同时,检查当前窗口中是否所有字符的出现次数与 need 中相同,如果是则说明找到一个符合条件的子串,将起始索引添加到结果数组 res 中。

最后返回结果数组 res,即为找到的所有符合条件的子串的起始索引数组。

这个算法的时间复杂度是 O(n),其中 n 为字符串 s 的长度。

code

/*** @param {string} s* @param {string} p* @return {number[]}*/
var findAnagrams = function (s, t) {// 需要的let need = {};// 窗口中的字符let window = {};for (let a of t) {// 统计需要的字符need[a] = (need[a] || 0) + 1;}// 左右指针let left = 0,right = 0;let valid = 0;let res = [];while (right < s.length) {// 即将移入窗口的字符let c = s[right];// 右移窗口right++;if (need[c]) {// 当前字符在需要的字符中,则更新当前窗口统计window[c] = (window[c] || 0) + 1;if (window[c] == need[c]) {// 当前窗口和需要的字符匹配时,验证数量增加1valid++;}}while (right - left >= t.length) {if (valid == Object.keys(need).length) {res.push(left);}let d = s[left];left++;if (need[d]) {if (window[d] == need[d]) {valid--;}window[d]--;}}}return res;
};

快慢指针

快慢指针方法会使用两个在数组(或序列/链表)中以不同速度移动的指针。该方法在处理循环链表或数组时非常有用。

应用场景:

  1. 处理链表或数组中的循环的问题
  2. 找链表中点或需要知道特定元素的位置

何时应该优先选择快慢指针,而不是上面提到的普通双指针方法?

有些情况不适合使用二指针方法,比如在不能反向移动的单链接链表中。使用快速和慢速模式的一个案例是当你想要确定一个链表是否为回文(palindrome)时。

移动零问题

题目如图

在这里插入图片描述

快慢指针解题思路

右指针自左向右遍历数组,右指针当前所指元素不为0的时候,交换左右指针对应元素,左指针右移,之后右指针继续遍历。
直到右指针遍历到数组的最后一位且右指针对应数字不为0
在这里插入图片描述

code

var moveZeroes = function (nums) {let len = nums.length;let left = 0;let right = 0;while (right < len) {//右指针所指的元素不为0时if (nums[right] !== 0) {// 交换左右指针的值let temp = nums[right];nums[right] = nums[left];nums[left] = temp;// 左指针右移left++;}// 右指针右移right++;}
};

移动零的其他解题方法

技巧一行code

var moveZeroes = function(nums) {nums.sort((a,b) => b? 0: -1)
};

如果 b 是真值,保持 a 和 b 的顺序不变;如果 b 是假值,将 a 排在 b 的前面。

这段代码实际上将数组中的元素按照它们在数组中出现的顺序排列,但将所有零值元素放在非零值元素的前面。

单个指针解题思路

1、先获取数组长度
2、然后循环遍历数组
3、当数组哪个位置是0 ,就删除这个这个位置的元素,然后在末尾补 0
从而就达到了 把所有0移动到数组末尾的要求且保持的非零元素的相对顺序
注意:当为0的元素删除后,下一个元素就会前进一位占据该位置,所以要从该位置在进行判断
当移动到末尾的元素,就不用再一次进行遍历了,所以遍历的长度要减去1位

code

var moveZeroes = function(nums) {//获取数组长度let len = nums.length;//循环遍历数组for(let i =0;i<len;i++){//当数组哪个位置是0 ,就删除这个这个位置的元素,然后在末尾补 0//从而就达到了 把所有0移动到数组末尾的要求且保持的非零元素的相对顺序if(nums[i]==0){nums.splice(i,1)nums.push(0)//当为0的元素删除后,下一个元素就会前进一位占据该位置,所以要从该位置在进行判断i--//当移动到末尾的元素,就不用再一次进行遍历了,所以遍历的长度要减去1位len--}}return nums
}

总结

    //循环遍历数组for(let i =0;i<len;i++){//当数组哪个位置是0 ,就删除这个这个位置的元素,然后在末尾补 0//从而就达到了 把所有0移动到数组末尾的要求且保持的非零元素的相对顺序if(nums[i]==0){nums.splice(i,1)nums.push(0)//当为0的元素删除后,下一个元素就会前进一位占据该位置,所以要从该位置在进行判断i--//当移动到末尾的元素,就不用再一次进行遍历了,所以遍历的长度要减去1位len--}}return nums

}

# 总结
算法的解决方法一定不唯一,所以上述的所有双指针算法可以为一些问题提供很好的解决思路,但是解法并不唯一,以上可供学习参考。

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

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

相关文章

IDM下载器激活

文章目录 1、Internet Download Manager简介2、Internet Download Managery应用3、Internet Download Managery下载 1、Internet Download Manager简介 Internet Download Manager (IDM) 是一款功能强大的下载管理软件&#xff0c;旨在帮助用户更高效地管理和加速其下载任务。它…

产业互联网助力预制菜出海 云创科技数据资产入表获批融资500万 新能源装备新质供应链创新协同平台启动 | 产业互联网观察第173期

产业互联网助力预制菜迈向国际市场 在第135届广交会上&#xff0c;一场聚焦“产业互联网赋能预制菜出海”的高端对话会隆重举办。本次活动由中国食品土畜进出口商会主办&#xff0c;云食界网络科技有限公司承办&#xff0c;吸引了众多政府领导、行业专家和企业代表参与。各界共…

MATLAB模拟退火算法、遗传算法、蚁群算法、粒子群算法

概况 模拟退火算法、遗传算法、蚁群算法、粒子群算法等算法&#xff0c;都是属于概率算法&#xff0c;不绝对&#xff0c;不迅速&#xff0c;能用其它方式解决的问题&#xff0c;不要用这些相对复杂的算法&#xff0c;比如有明确的线性关系或者非线性对应关系。这里的概率算法…

流程详解!2024年成都市发明专利申请流程及各阶段操作要点

一、受理阶段 时间期限&#xff1a; 电子申请2天内&#xff0c;纸质申请当天现场提交&#xff0c;邮寄约为半月。 申请人&#xff1a; 1. 委托专利代理机构&#xff0c;签订委托代理协议和保密协议等&#xff1b; 2. 提供原始技术资料和个人以及单位信息等&#xff1b; 3…

解决ssh使用服务器运行代码时中断问题

https://www.cnblogs.com/mchina/archive/2013/01/30/2880680.htmlhttps://www.cnblogs.com/mchina/archive/2013/01/30/2880680.html

【JavaWeb】Servlet+JSP+EL表达式+JSTL标签库+Filter过滤器+Listener监听器

需要提前准备了哪些技术&#xff0c;接下来的课才能听懂&#xff1f; JavaSE&#xff08;Java语言的标准版&#xff0c;Java提供的最基本的类库&#xff09; Java的开发环境搭建Java的基础语法Java的面向对象数组常用类异常集合多线程IO流反射机制注解Annotation… MySQL&…

经常睡不好觉?试试用上华为手环9新升级的睡眠监测功能

睡眠问题是不是经常困扰着你呢&#xff1f;听说&#xff0c;华为手环9的睡眠监测功能升级了&#xff0c;无论是入睡前、睡眠中还是睡醒后&#xff0c;都能够帮助我们改善睡眠&#xff0c;让我们告别糟糕的睡眠质量&#xff01; 睡觉前&#xff0c;打开华为手环9的睡眠模式&…

图表控件LightningChart .NET中文教程 - 创建3D网格模型实时着色应用

LightningChart.NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科学…

住房“以旧换新”,后续需要关注啥?

政治局会议提出对房地产“消化存量、优化增量”&#xff0c;地产政策重心或转向去库存&#xff0c;各地住房“以旧换新”政策引发关注。“以旧换新”分为国企收购、市场化交易、购房补贴三种模式&#xff0c;其中国企收购政策力度较大&#xff0c;消化商品房库存增加保障房供给…

第4章 处理多种内容类型

4.1 内容类型Content-Type 4.1.1 什么是Content-Type Content-Type是HTTP协议头中的一个字段&#xff0c;它用于描述HTTP请求或响应中所传输的实体数据的媒体类型&#xff08;MIME类型&#xff09;。Content-Type告诉客户端和服务器端所传输的数据的实际内容类型&#xff0c;使…

数据库(MySQL)基础:约束

一、概述 1.概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2.目的&#xff1a;保证数据库中数据的正确、有效性和完整性。 3.分类 约束描述关键字非空约束限制该字段的数据不能为nullnot null唯一约束保证该字段的所有数据都是唯一…

Python深度学习基于Tensorflow(2)Tensorflow基础

文章目录 基本操作数据转换和数据生成操作形状数据提取和保存变量Numpy和Tensorflow的比较 计算图静态图动态图自动图 自动微分使用Tensorflow 实现回归 首先是Tensorflow的安装&#xff0c;由于可能会出现版本冲突&#xff0c;最好在conda环境安装&#xff0c;同时&#xff0c…

什么是IT服务台?

IT服务台是组织中的单一联系点&#xff0c;负责解决所有与 IT 相关的问题、查询和请求。IT服务台也称为技术支持、支持中心、信息中心、IT 解决方案中心或技术支持。 IT 服务台的多用途角色可实现多个目标&#xff0c;例如快速解决问题、培养用户满意度、提高组织流程效率以及提…

华为ensp中BFD和OSPF联动(原理及配置命令)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 BFD通常指的是双向转发检测。BFD是一个旨在快速检测通信链路故障的网络协议&#xff0c;提供了低开销、短延迟的链路故障检测机制。它主要用于监测两个…

【复试分数线】C9历年分数线汇总(第二弹)

今天我将分析C9中主要考信号的5所院校&#xff1a;复旦大学、上海交通大学、南京大学、哈尔滨工业大学、西安交通大学。 这次会为大家整理四电四邮的整理了近三年各院校的复试分数线作为参考&#xff0c;大家可以参考&#xff01; 大多数院校采取的是1.2:1差额的形式复试。举…

egg数据统计之mysql数据库创建视图并可当表使用并查询

打开视图---->新建视图-----> 运行sql ----> 保存 统计地区的愿望数量 sql语句 select user.id AS id,count(userplant.userid) AS amount,user.locationid AS locationid,user->location.name AS locationname from ((userplants userplant left join users us…

macOS12安装 php7.1和apache

1. 安装php 7.1 macOS12不再自带php brew tap shivammathur/php 查看可安装版本 brew search php 安装指定版本&#xff08;禅道适用PHP运行环境(7.0/7.1/7.2版本)&#xff09; brew install php7.1 环境配置 vim ~/.zshrc export PATH"/usr/local/opt/php7.1/bin:…

Rust 使用egui创建一个简单的下载器demo

仓库连接: https://github.com/GaN601/egui-demo-download-util 这是我第一个rust gui demo, 学习rust有挺长时间了, 但是一直没有落实到实践中, 本着对桌面应用的兴趣, 考察了slint、egui两种框架, 最后还是选择了egui. 这篇博客同时包含我当前的一些理解, 但是自身技术有限,…

Java 7大排序

&#x1f435;本篇文章将对数据结构中7大排序的知识进行讲解 一、插入排序 有一组待排序的数据array&#xff0c;以升序为例&#xff0c;从第二个数据开始&#xff08;用tmp表示&#xff09;依次遍历整组数据&#xff0c;每遍历到一个数据都再从tmp的前一个数据开始&#xff0…

LeetCode-2960. 统计已测试设备【数组 模拟】

LeetCode-2960. 统计已测试设备【数组 模拟】 题目描述&#xff1a;解题思路一&#xff1a;模拟解题思路二&#xff1a; 一次遍历&#xff0c;简洁写法解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个长度为 n 、下标从 0 开始的整数数组 batteryPercentages &#xf…