当前位置: 首页 > news >正文

专题1:双指针

一.双指针概述

双指针一般充当的是数组下标,而不是真正的指针,但是可以起到和指针一样的效果:找到当前指针所指向的数据。双指针一般分为两种形式,一般是对撞指针和左右指针。

对撞指针:一般出现在顺序结构,用于快速遍历,两个指针从两端向中间逐渐逼近,当两个指针相遇或者指针错开该循环就会终止:left == right || left > right。

快慢指针:又称为龟兔赛跑算法,其基本思想就是利用两个移动速度不同的指针在数组或者链表中的序列结构上移动。这种方法对于处理环形链表以及数组非常有用,比如判断带环链表的入口点。快慢指针实现的方式有很多种,最常用的是:在一次循环中,每次让慢指针走一步,快指针走两步。滑动窗口是快慢指针的一种特殊形式。

二.双指针OJ题目

以下我会使用一些OJ题目来阐述双指针算法的使用。

1.移动零 - 力扣

这道题目是让我们把0移动到数组末尾,同时保持非0元素原来的相对顺序,这种类型的题目就很适合用双指针解决。

我们在看到这道题的时候下意识就会使用对撞双指针来解决这个题目,但是这个题目要求需要保持原来非0元素的相对顺序,但是使用对撞指针会改变这个结果,所以我们此时只能使用同向的快慢双指针。

假设数组是[ 0, 1, 0, 3, 12 ],交换以后应该是 [ 1, 3 , 12, 0, 0, 0 ],我们可以将数组以slow指针分为非0元素和0元素,在slow指针包括slow指针的左边都是非0元素,也就是说slow + 1的那个元素一定是0,那么此时我们只需要通过fas找到一个非0元素交换,然后slow++继续划分数组即可。

slow一开始是-1的位置,第一次循环fast == 0,fast++ 第二次循环 fast == 1,此时找到了非0,slow++,因为slow是最后一个非0元素,然后swap(slow,fast).,接着fast++,因为此时的fast以及为0了,需要继续遍历下一个元素。

第三次循环fast == 0,fast++.第四次循环fast == 3,此时找到非0,slow++,然后swap(slow,fast).循环直到fast将数组遍历完数组自然就被分成了0和非0的两个部分。

大家可以先按上面的思路再和我的这个代码进行比较。

2.复写0 - 力扣

题目描述:

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西

该题目的意思是复写0以后要保持原来数组的大小并且只能在原数组实现,所以就要通过cur指针遍历数组找到0的位置,然后通过dest修改当前位置以及下一个位置为0。但是如果从前往后复写就会将后面的数据覆盖,所以我们需要从后往前复写,那么就需要找到最后一个复写的数。

1.找最后一个复写的位置:

dest从-1位置开始,cur从0开始遍历,当cur为非0时,此时dest++,然后判断dest是否到达数组的最后一个位置,接着cur++;当cur为0时,dest+=2,判断dest是否到达数组的最后一个位置,然后cur++。当dest>=n-1的时候,此时cur的位置就是最后一个复写的位置。

但是有一种特殊情况,就是当复写的最后一个值为0的时候此时dest加等2以后就会变为n,那么dest就越界了,我们需要对这个情况单独处理。

2.开始复写:

复写的操作就是从当前的cur位置开始,先判断此时的cur是否为0,非0 dest = cur,然后cur--,dest--。如果此时cur为0,那么dest要将当前dest位置和下一个都修改为0,然后cur--。当cur < 0的时候,复写结束。

代码运行流程:

以数组[1,0,2,3,0,4,5,0]为例,此时cur指向4。

第一次循环的时候cur不等于0,dest = cur,cur--,dest--。

第二次循环cur等于0,dest当前位置以及下一个位置改为0,然后cur--,dest-=2。

倒数第二次循环,cur执行下标为1的0,此时下标为1,2的值都改为0,cur--,dest-=2,然后进入最后一次循环此时cur和dest都指向下标0,dest=cur以后cur--,dest--跳出循环,复写结束。

具体的代码以及测试

3.快乐数-力扣

题目描述:

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

「快乐数」 定义为:

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

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

这里拿官方给的两个例子来说明规律

n = 19的时候会经历以下四步计算,最终结果得到1,说明19是快乐数

n = 2的时候会经历以下的计算

经历到第九步的时候我们会发现他又回到了4,也就是进入了一个循环,所以能够得到两种结果,1:该数是快乐数,循环内的值一直是1

2:该数不是快乐数,但是会进入一个循环,循环的开始是计算中的某一个值

证明:经历过一次变换以后的最大值为9^2 * 10 = 810(int 能表示的最大值为2^31-1,选择一个更大的数9999999999来比较),所以变化区间在[1,810],根据鸽巢原理,一个数变化811次以后鄙人成环,所以这道题也就转换为了类似判断链表是否带环(这里一定带环),我们就可以用快慢双指针,循环中快指针始终会追上慢指针,判断当快慢指针相遇的时候那个值是否为1即可。                                    

定义一个变量为fast,一个变量为slow,一次循环中fast每次计算两次,slow计算一次,判断两数是否相等,相等结束循环,最后判断当前值是否为1即可。

具体代码以及实现:

4.盛水最多的容器-力扣

题目描述:

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

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

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

说明:你不能倾斜容器。

其实就是水桶效应,在一个有长短不同木条的水桶中决定水桶能装多少水的深度的是最短的那一根。这道题目的暴力算法就是每个数枚举一次,于后面的数计算这个水桶的最大容积。优化计算,我们可以直接计算出一段区间的最大容积,然后比较各个区间的最大容积。

证明:当我们固定左边端点的时候,此时right=7的固定左边端点为1的时候容器的容积最大,右边端点无论怎么变化都不会超过此时。所以就可以将左端点向右移动一位,找此时下一个区间内的最大容积。此时较小的端点是右端点为7,那么此时固定右端点,左端点无论怎么移动此时都是最大容积,所以右端点向左移动一位,找下一个区间的最大值。循环时比较每个区间的最大容积找到最大容积。注意:这里移动小的端点是因为此时决定容积大小的是小端点,此时w是最大的,当该端点为固定一边所能枚举出来最大容积,移动小端点才可以找到固定每个小端点的时候的最大容积。

具体代码以及测试:

5.有效三角形个数

题目描述:

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

暴力解法:写三个for循环枚举出来三个数再判断是否能构成三角形,但是三层循环这里是一定会超时的。

我们可以找一种新思路:在这个[ 4, 2, 3, 4 ]示例中,我们可以对数组进行排序,然后就会得到图中的这样,由于判断三角形的构成是两边大于第三边,所以我们可以每次固定最大的那一边,然后找次小的两边。当left + right > cur的时候,说明当固定right的时候,在left和right - 1这段区间内的所有数都可以与cur组成三角形,因为left是最小的,left后面的数都大于它,那么nums+ right > cur。当left + right <= cur的时候,向右移动left,增大left,直到找到一个left最小值能够与此时right组成三角形,再计算区间内有多少个三角形。当找到一个区间内的三角形个数以后,--right,去找下一个次大值的时候能组成多少个三角形,注意此时left不必要返回0从新开始,因为较大的right与left组成的时候是恰好能够大于cur的,此时如果left--那么原来的left + right 肯定小于cur,更别说right--了。

具体代码以及测试:

6.查找价值为目标值的两个商品-力扣

题目描述:

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

暴力解法:两层for循环遍历,找到目标值就返回。

优化解法:由于该数组是递增的,所以我们可以选出一个较大的数right,一个较小的数相加left,如果大于目标值,right--此时总和就减小,如果小于目标值left++此时总和变大,只要找到相等的值即返回下标为left,和right的数据。

使用官方示例 price = [8, 21, 27, 34, 52, 66], target = 61 来证明:

第一次循环left = 8,right = 66,sum = 74,sum > target,所以此时right--指向52

第二次循环left = 8,right = 52,sum = 60,sum < target,所以此时left++指向21

第三次循环left = 21,right = 52,sum = 74,sum > target,所以此时right--指向34

第三次循环left = 21,right =34,sum = 55,sum < target,所以此时left++指向27

第四次循环left = 27,right =34,sum = 62,sum = target,循环结束

具体代码以及测试结果:

7.三数之和

题目描述:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

我们可以使用上面两数之和的思想来解决这个题目

排序:首先对输入数组进行排序。

外层循环:固定一个数 nums[i],然后在剩下的数组中使用双指针法寻找两个数,使得这三个数的和为零。

双指针法:对于固定的 nums[i],设置 leftright 两个指针,分别指向 i+1 和数组末尾,根据三数之和与零的比较移动指针。

去重:在找到有效的三元组以及移动指针时,跳过重复的元素以避免结果中出现重复的三元组

思路证明:

示例:

具体流程:

第一次循环选择i值为-5,left = i + 1的值,right = n - 1的值,sum = i + left + right,此时sum小于0,说明left需要增大,所以left++。left一直++到值为2的地方,此时 i + left + right == 0,那么这三个数可以放入数组中,然后left++,right--,left > right,此次选择的i值的循环结束。

i值自动往后选择下一个,left = i + 1,right = n-1,然后判断sum,发现sum < 0,left一直++到值1的位置,此时sum==0,那么这三个数可以放入数组中,然后left++,right--,left == right,此次选择的i值的循环结束。

i值此时自动选择下一个值为-4,与上一个选择的值相等,跳过此次选择的循环。循环往复,当i值大于0的时候跳出for循环,因为i值表示的是最小的那个值。

需要注意的是,如果当i=-4,而前面有两个1,两个3,此时left为1,right为3,然后插入这个数组以后需要排除left后面相同的1,以及right前面的3,防止有重复的元素

具体代码以及测试:

三.总结

优势:

  1. 降维打击
    把原本必须套两层循环的暴力检索压成一层:排好序后让左右指针从两端向中间走,每一步都能排除掉“不可能”的半截区间,把 O(n²) 直接削成 O(n)。

  2. 在线处理
    不必把中间结果全部算出来存着;窗口或指针滑到哪儿,当前答案就即时更新到哪儿。例如滑动窗口的最大值,随着右沿右扩、左沿左缩,队首元素就是这一刻的全局最值,无需事后二次扫描。

  3. 天然去重
    序列一旦有序,重复元素必然连续出现;指针只需在移动前后“跳过相同段”,就能把重复三元组、四元组等一次性甩开,省掉哈希表或 set 的额外开销。

  4. 空间友好
    整个算法只保留两三个游标变量,没有辅助栈、没有复制数组,也没有递归层,额外空间严格 O(1),对内存紧张或超长输入尤其友好。

http://www.dtcms.com/a/613556.html

相关文章:

  • 基于Vue的鲜花销售系统33n62(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
  • sdc 编写笔记
  • Rabbit MQ:概述
  • 建站之星管理中心注册海外公司
  • 【HarmonyOS】ArkWeb——从入门到入土
  • 微网站 微信网站优化服务是什么意思
  • VS Code 隐藏顶部标题栏中间的文字
  • 珠海网站哪家好如何给网站流量来源做标记通过在网址后边加问号?
  • Rust入门
  • Rust入门 之一
  • “伪”局域网
  • C语言编译软件Mac | 在Mac上选择最合适的C语言编译工具
  • 怎么样建设一个网上教学网站网页版微信二维码不能直接识别
  • Linux BPF 技术深度解析:从原理到实践
  • 高端网站报价wordpress如何添加背景音乐
  • C# 对多个任务进行符合管理
  • 在Eclipse IDE for Embedded C/C++ Developers软件中定义的宏,编译C源文件时编译器无法找到宏定义!
  • 从局域网到全网可用!PDFMathTranslate 翻译工具的进阶使用法
  • 深入理解 JavaScript 异步编程:从单线程到 Promise 的完整指南
  • 怎么自己做歌曲网站沈阳网站建设方案策划
  • 电脑卡顿因重复文件?AllDup无安装版快速查重+批量删除 文件管理混乱?AllDup多模式查重工具,Python开发者也能高效用
  • Dubbo Mock机制详解:服务降级与本地测试的利器
  • JDBC与事务的协同:ThreadLocal的巧妙运用
  • 底层视觉及图像增强-项目实践理论补充(十六-0-(13):HDR技术全链路解析:从原理到LED显示工程实践):从奥运大屏,到手机小屏,快来挖一挖里面都有什么
  • 深圳服务平台网站网站提示域名解析错误怎么办
  • 论文阅读13——基于大语言模型和视觉模态融合的可解释端到端自动驾驶框架:DriveLLM-V的设计与应用
  • 考研408--数据结构--day2--顺序表及其增删改查
  • 软件演示环境动态扩展与成本优化:基于目标跟踪与计划扩展的AWS Auto Scaling策略
  • 网站设计的资质叫什么花蝴蝶韩国免费视频
  • AI Agent 之工具使用:从函数定义到实际应用