利用双指针法解题

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


目录

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

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

双指针的类型

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

两个指针都从同一方向移动,通常一个指针移动得快,另一个移动得慢。
常用于寻找子数组、滑动窗口问题、链表环检测等。
下面我将会从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& 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-1) break;
cur++;
}
在编写这部分代码时,我也有很多疑问,比如为什么将 dest 初始化为 -1 而不是0,cur 又为什么初始化为0。

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

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

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

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

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

代码实现
class Solution {
public:
void duplicateZeros(vector& arr) {
int n=arr.size();
int dest=-1,cur=0;
while(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& 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& 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 twoSum(vector& 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 != j、i != k 且 j != k ,并且这个三元组还不能重复,比如测试用例的一,其中构成0的三元组中有两个[-1,0,1],但是只算一个。

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

注意:

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

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

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

class Solution {
public:
vector> threeSum(vector& nums) {
vector> ret;
int n=nums.size();
sort(nums.begin(),nums.end());
for(int first=0;first0 && nums[first] == nums[first - 1])
{
continue;
}
int third=n-1;
int target = -nums[first];
for(int second=first+1;secondfirst+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循环

class Solution {
public:
vector> fourSum(vector& nums, int target) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector> 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 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;
}
};

class Solution {
public:
vector> fourSum(vector& nums, int target) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector> 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;
}
};
到这里就结束了,

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

目录
相关文章
|
SQL 存储 数据可视化
Ganos H3地理网格能力解析与最佳实践
本文介绍了Ganos H3的相关功能,帮助读者快速了解Ganos地理网格的重要特性与应用实践。H3是Uber研发的一种覆盖全球表面的二维地理网格,采用了一种全球统一的、多层次的六边形网格体系来表示地球表面,这种地理网格技术在诸多业务场景中得到广泛应用。Ganos不仅提供了H3网格的全套功能,还支持与其它Ganos时空数据类型进行跨模联合分析,极大程度提升了客户对于时空数据的挖掘分析能力。
|
11月前
|
机器学习/深度学习 传感器 人工智能
开源AI视频监控系统在监狱安全中的应用——实时情绪与行为分析、暴力预警技术详解
针对监狱环境中囚犯情绪波动和复杂人际互动带来的监控挑战,传统CCTV系统难以有效预警暴力事件。AI视频监控系统基于深度学习与计算机视觉技术,实现对行为、情绪的实时分析,尤其在低光环境下表现优异。该系统通过多设备协同、数据同步及自适应训练,确保高精度识别(95%以上)、快速响应(&lt;5秒),并具备24小时不间断运行能力,极大提升了监狱安全管理的效率与准确性。
937 1
|
5月前
|
Linux 编译器 vr&ar
Linux的动态库与静态库
静态库在编译时直接嵌入到最终的可执行文件中。
144 0
|
5月前
|
存储 人工智能 Unix
Linux常见指令汇总
最常见的就是 ll (为ls -l的省略)
199 0
|
5月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
201 0
|
11月前
|
人工智能 智能硬件
SPAR:智谱 AI 推出自我博弈训练框架,基于生成者和完善者两个角色的互动,提升了执行准确度和自我完善能力
SPAR 是智谱团队推出的自我博弈训练框架,旨在提升大型语言模型在指令遵循方面的能力,通过生成者和完善者的互动以及树搜索技术优化模型响应。
311 0
SPAR:智谱 AI 推出自我博弈训练框架,基于生成者和完善者两个角色的互动,提升了执行准确度和自我完善能力
|
前端开发 JavaScript 数据库
Web的B/S架构
Web的B/S架构
1384 1
|
域名解析 负载均衡 网络协议
信息收集——绕过CDN查找真实IP(最实用的方法)
信息收集——绕过CDN查找真实IP(最实用的方法)
7574 0
信息收集——绕过CDN查找真实IP(最实用的方法)
|
机器学习/深度学习 监控 数据可视化
关于运动员伤病预测数据集的探索(上)
关于运动员伤病预测数据集的探索
398 1
|
Java Android开发 C++
Kotlin vs Java:选择最佳语言进行安卓开发
【4月更文挑战第13天】Java曾是安卓开发的主流语言,但Kotlin的崛起改变了这一局面。Google在2017年支持Kotlin,引发两者优劣讨论。Java以其成熟稳定、强大生态和跨平台能力占优,但代码冗长、开发效率低和语言特性过时是短板。Kotlin则以简洁语法、空安全设计和高度兼容Java脱颖而出,但社区和生态系统仍在发展中,可能存在学习曲线和性能问题。选择语言应考虑项目需求、团队熟悉度、维护性、性能和生态系统。无论选择哪种,理解其差异并适应新技术至关重要。
872 4