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

利用双指针法解题

目录

双指针的简单介绍

双指针的类型

题目一:283. 移动零 - 力扣(LeetCode)

 题目意思解释

算法原理

 代码实现

 题目二:1089. 复写零 - 力扣(LeetCode)

 题目意思解释

算法原理

 代码实现

题目三:202. 快乐数 - 力扣(LeetCode)

题目意思解释 

 算法原理

 代码实现

题目四:11. 盛最多水的容器 - 力扣(LeetCode)

题目意思解释 

 算法原理

  代码实现

题目五: 611. 有效三角形的个数 - 力扣(LeetCode)

  题目意思解释

 算法原理

代码实现 

题目六:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

题目意思解释 

算法原理

代码实现

题目七:15. 三数之和 - 力扣(LeetCode)

题目意思解释

算法原理

代码实现

题目八:18. 四数之和 - 力扣(LeetCode)

题目意思解释

算法原理

代码实现


双指针的简单介绍

双指针是一种常用的算法技术,主要用于处理数组、链表等线性结构中的问题。它通过使用两个指针在数据结构中同时移动,从而达到有效解决问题的目的。这种方法通常能够减少空间复杂度或时间复杂度,或者使代码更加简洁。

但是在这里的指针并非真正的指针

它可以是数组的下标,亦或是链表的第k个值。

双指针的类型

相向双指针:

  • 两个指针分别从数组的两端向中间移动。
  • 常用于寻找数组中两数之和、判断回文字符串、合并两个有序数组等问题。

同向双指针

  1. 两个指针都从同一方向移动,通常一个指针移动得快,另一个移动得慢。
  2. 常用于寻找子数组、滑动窗口问题、链表环检测等。

下面我将会从8个力扣的题来讲解利用双指针来解题。

每个题将会从对题目的意思介绍,算法的原理,编写代码的注意点

有的题目机译没什么问题就不一句一句解释了

题目一:283. 移动零 - 力扣(LeetCode)

 题目意思解释

该题目给了一个数组nums,然后让你编写一个函数,对数组进行操作后,使得该数组分为两部分,0全部在数组的后半部分,前半部分全为非0,并且非零部分的元素还保持着原来数组的相对位置,相对位置的意思就为:原数组中a在b前面,那么进行操作过后还是a在b前。并且,该题目还要求只能在数组的原地进行操作。

算法原理

如果我们先抛开计算机的编程思想,我们在现实生活中遇见这样问题,那么我们肯定想着先按照相对位置把所有非0元素拿出来排好,在把0依次放在其后,但是在编程中这就不是在数组原地进行操作。

经过思考后发现可以依次交换如果两个相邻的数,只需要把非0的与0进行交换。

就比如只需要先1 0交换后再 0 3,最后0 12就达到了要求,那么我们把这种思想转换为编程思想即可,这种思想就为双指针思想。

我们只需要定义两个“指针”

然后把数组分为两个区间,其中又分为三个小区间。

对于指针的操作如下: 

这里用的是同向指针

 代码实现

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

 题目二:1089. 复写零 - 力扣(LeetCode)

 题目意思解释

给了一个长度固定的整形数组,然后将所有0全部复写一边,并将其后面的所有元素全部向右移动,还需要对该数组就地修改,不返回任何东西。

算法原理

理解题目后,如果对时间复杂度比较敏感,算法就必须从后往前进行遍历。这是因为如果从前往后依次复写,可能会出现覆盖问题,并且时间复杂度将达到 O(n²)。

采用双指针算法,从后往前遍历会更加高效。以第一个测试用例为例,我们可以清楚地知道最后一个操作数是4,因此只需将4 覆盖到最后一个0 的位置,然后再依次将0复写到前面的两个位置。这样,我们可以高效地向前遍历并进行覆盖。

在实现时,可以参考第一题的代码,使用双指针的方法来完成。以下是操作示意图,具体实现可以根据这个思路进行编码。

那么重要的怎么明确找到最后一个位置,也就是怎么代码实现使其cur指针指向的是4,而不是0或者5,

那么这里是用到的快慢指针,我先直接给出代码,代码所要实现的就为

  • 如果arr[cur]为0的话dest+=2  
  • 如果arr[cur]不为0的话dest++ 
  • dest走到最后一个元素结束
  • 最后cur++

        int n=arr.size();int dest=-1,cur=0;while(dest<n){if(arr[cur]){dest++;}else{dest+=2;}if(dest>=n-1) break;cur++;}

在编写这部分代码时,我也有很多疑问,比如为什么将 dest 初始化为 -1 而不是0,cur 又为什么初始化为0。

要理解这些初始化的原因,从结果反推起始的设置会更加清晰。

从结果倒推,我们可以得出以下结论:因此,dest 初始化为 -1,而 cur 初始化为0。

 

但是根据算法原理一点点实现代码,然后运行提交,会报越界的错误,这是因为有种特殊的情况,比如下面的这个数组:

 

经过上面的代码后,cur与dest的指向情况如下:

因此,当 dest 等于 n 时,需要进行特别处理。

 代码实现

class Solution {
public:void duplicateZeros(vector<int>& arr) {int n=arr.size();int dest=-1,cur=0;while(dest<n){if(arr[cur]){dest++;}else{dest+=2;}if(dest>=n-1) break;cur++;}if(dest==n){arr[n-1]=0;dest-=2;cur--;}while(cur>=0){if(arr[cur]){arr[dest]=arr[cur];cur--;dest--;}else{arr[dest]=arr[cur];dest--;arr[dest]=arr[cur];dest--;cur--;}}}
};

题目三:202. 快乐数 - 力扣(LeetCode)

题目意思解释 

题目的意思很好理解,那么这里我就不在解释了,我说明需要注意的地方:这个题中又这样一句话:然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1

永远的无限循环就是他题目中的2,他会陷入循环,并且循环中一直不会变为1.

但起始1也是一种循环,但他的循环一直为1. 

 算法原理

这道题的算法原理其实是引用了环形队列的快慢指针的思想,(题目:141. 环形链表 - 力扣(LeetCode)))

我这里举一个生活中跑步的案例解释一下:假设小明与小刚同学同时从宿舍出发去操场跑步,小明的速度是小刚速度的二倍,那么总有那么一秒初(不是一秒中也不是末)小刚与小明会相遇。

所以我们以这种的方法就可以判断是以1循环还是不规则的循环

 代码实现

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

题目四:11. 盛最多水的容器 - 力扣(LeetCode)

题目意思解释 

依旧是这一题的意思很好理解,需要提一嘴的就是一个木桶所能装多少水是由其短板决定的。所以这一题的容器高是由两端木板中的短板决定。

 算法原理

对于一个容器的容量计算公式  v=w*h  

那么我们还是利用双指针来解决这道题,需要用到的是相向双指针,定义一个right指针,与left指针,需要解决的是什么情况left++,什么情况下right--,

从公式来说,无论是left++,还是right--,他都会使得w减小,但是w减小的话,如果v要想增加,那么只能使得h增大。然而一个容器内的h是由短板决定,所以就很容易想到要将小的那个进行调整。

这也很好理解,如果我们对高的进行调整,就算遇到一个更大的数,那么h依旧不会发生改变,遇见小的只会更小,不符合我们的要求。

  代码实现

class Solution {
public:int maxArea(vector<int>& height) {int n=height.size();int r=n-1,l=0;int mx=0;while(r>l){int w=r-l;int h=min(height[r],height[l]);mx=max(mx,h*w);if(height[r]>height[l]) l++;else r--;}return mx;}
};

题目五: 611. 有效三角形的个数 - 力扣(LeetCode)

  题目意思解释

同样这道题的描述依旧很详细,需要提一嘴的就是如果该数组为[ 2 , 2 , 2 , 3 , 4 ],

那么2 , 2 ,3 与2 , 2 , 3,是不同的两个组,应该计为2,不为1。

同样需要补充的一点就是构成三角形的规定:任意两边之和大于第三边。

 算法原理

我们这道题就是运用到的为 任意两边之和大于第三边 ,但是如果将三个数都进行两两组合,就显得过于麻烦,这时候需要我们先排序,排序之后,任取三个数,相对位置的前两个数相加如果大于第三个,那么其余组合一定大于另外一边。这也很好理解,如果三个数两个小的数相加大于最大的哪一个,那么其余组合一定成立。

因为有三个操作数,那么是不是要用三指针解决呢?

其实不然,我们不妨套一层循环,定一,另外两个用双指针解决子问题。

那么需要解决的就是定谁?

令 两个小的数为 a ,b,最大的为 c ,如果a+b>c则构成三角形

如果定a,那么b与c必须要定位双向双指针,在a+b<=c时,b调大或c调小都可以,会有歧义,但是a+b>c时,没有可以调整的指针使得当前状态改变。定b也是这样分析。

所以要定c

如果此时a+b>c那么如果a增大已经为a+b>c,所以此时只要a小于b都可以构成三角形,

这时一共有b-a个数。

如果a+b<=c,那么就需要调整b,使得找到a+b>c的情况在进行上面操作。

代码实现 

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

题目六:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

题目意思解释 

题目描述很详细,并且也没有需要注意的,那么这就不解释了。

算法原理

这一道题应该算是所有题中最好写的,甚至比力扣第一题还简单好多。

这就不在多说了。

只给一点提示:需要用的双指针类型为双向双指针。

需要注意的是,自己熟悉一下本题的返回形式,还有注意保证函数在所有情况下都要有返回值,在没有找到的情况下,力扣一般是返回负数的。

代码实现

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

题目七:15. 三数之和 - 力扣(LeetCode)

题目意思解释

 同样这道题理解起来很容易,但是需要注意的一点就是:[nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,并且这个三元组还不能重复,比如测试用例的一,其中构成0的三元组中有两个[-1,0,1],但是只算一个。

算法原理

其实算法实现思路还是与构成三角形的那个一样还是定一,然后剩余两个用双向双指针法。如果是自己能完全不参考任何代码可以写出,那么这一题完全不是问题。

注意:

一定要符合题中的要求,三元组不可以重复,一定要保证每一个三元组不同,那么需要注意的就是两次的first或者second或者third不可以相同,需要去重。

代码实现

代码实现给两个版本,一个是for循环,一个是while循环

while

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> ret;int n=nums.size();sort(nums.begin(),nums.end());for(int first=0;first<n;first++){   if(nums[first]>0) break; //小优化  如果first大于0 ,first又为最小,那么无论如何都构不成0if (first>0 && nums[first] == nums[first - 1])// 去重{continue;}int third=n-1;int second=first+1;int target = -nums[first];while(second<third){if(nums[second]+nums[third]>target){third--;}else if(nums[second]+nums[third]<target){second++;}else{ret.push_back({nums[first],nums[second],nums[third]});second++,third--;while(second<third && nums[second]==nums[second-1]) second++;// 去重while(second<third && nums[third]==nums[third+1]) third--;// 去重}}}return ret;}
};

for

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> ret;int n=nums.size();sort(nums.begin(),nums.end());for(int first=0;first<n;first++){   if (first>0 && nums[first] == nums[first - 1]){continue;}int third=n-1;int target = -nums[first];for(int second=first+1;second<n;second++){if(second>first+1 && nums[second]==nums[second-1]){continue;}while( third>second&& nums[second]+nums[third]>target){third--;}if(second>=third) break;if (nums[second] + nums[third] == target){ret.push_back({nums[first],nums[second],nums[third]});}}}return ret;}
};

 

题目八:18. 四数之和 - 力扣(LeetCode)

题目意思解释

意思与上一题完全一致 

算法原理

同样为双指针,但是需要定二,另外两个为双向双指针。其实这道题就是在外壳套一个三数之和。

很简单。

代码实现

代码实现给两个版本,一个是for循环,一个是while循环

while

class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> ret;for (int first = 0; first < n; first++){if (first > 0 && nums[first] == nums[first - 1]){continue;}for (int second = first + 1; second < n; second++){if (second > first+1 && nums[second] == nums[second - 1]){continue;}int fourth = n - 1;int third=second+1;long long ans = (long long)target - nums[first] - nums[second];while(third<fourth){if (nums[third] + nums[fourth] > ans){fourth--;}else if(nums[third] + nums[fourth] < ans){third++;}else{ret.push_back({ nums[first],nums[second],nums[third],nums[fourth] });third++,fourth--;while(third<fourth && nums[third]==nums[third-1]) third++;while(third<fourth && nums[fourth]==nums[fourth+1]) fourth--;}}}}return ret;}
};

for

class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> ret;for (int first = 0; first < n; first++){if (first > 0 && nums[first] == nums[first - 1]){continue;}for (int second = first + 1; second < n; second++){if (second > first+1 && nums[second] == nums[second - 1]){continue;}int fourth = n - 1;long long ans = (long long)target - nums[first] - nums[second];for (int third = second + 1; third < n; third++){if (third > second + 1 && nums[third] == nums[third - 1]){continue;}while (fourth > third && nums[third] + nums[fourth] > ans){fourth--;}if (third >= fourth) break;if (nums[third] + nums[fourth] == ans){ret.push_back({ nums[first],nums[second],nums[third],nums[fourth] });}}}}return ret;}
};

到这里就结束了,

那么为了检测,可以去力扣上搜一些双指针的题去练习

977.有序数组的平方

题目链接:. - 力扣(LeetCode)

189.轮转数组

题目链接:. - 力扣(LeetCode)

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

题目链接:. - 力扣(LeetCode)

344.反转字符串

题目链接:. - 力扣(LeetCode)

557.反转字符串中的单词 III

题目链接:. - 力扣(LeetCode)

557.反转字符串中的单词 III

题目链接:. - 力扣(LeetCode)

19.删除链表的倒数第 N 个结点

题目链接:. - 力扣(LeetCode)


http://www.mrgr.cn/news/62236.html

相关文章:

  • linux 高级 I/O
  • 51单片机STC8G串口Uart配置
  • vite5 打包项目兼容ie和低版本chrome
  • 【2024工业图像异常检测文献】HGAD: 基于层次高斯混合归一化流建模的统一异常检测方法
  • 重学SpringBoot3-怎样优雅停机
  • 如何使用的是github提供的Azure OpenAI服务
  • RNN在训练中存在的问题
  • 大模型入门综述---从模型,训练,部署全方面认识大模型
  • 如何解决Matplotlib报错:none of the following families were found: SimHei
  • ReactNative Fabric渲染器和组件(5)
  • 统信UOS下启动图形界面应用工具monitor报JAVA相关错:An error has occurred. See the log file
  • 《高频电子线路》 —— 高频谐振功放
  • RK3568平台开发系列讲解(I2C篇)I2C 上拉电阻
  • 统信UOS下启动图形界面应用工具manager报错:No protocol specified的解决办法
  • 不使用三方软件,win系统下禁止单个应用联网能力的详细操作教程
  • C语言实现堆排序
  • Redis 线程控制 问题
  • C语言实现选择排序
  • 主成分分析(PCA)在医学数据分析中的神奇力量
  • 当AI取代真相,大模型如何一步步诱骗了人类的文明?
  • ubuntu增加swap交换空间
  • 车载中控系统的UI自动化测试实践
  • VB.NET中如何利用Windows Forms进行桌面应用开发
  • HCIP-HarmonyOS Application Developer V1.0 笔记(二)
  • 代码编辑器 | Visual Studio Code v1.95.0
  • C语言:动态内存管理【上】