文章目录
双指针算法
双指针算法可以实现对于时间复杂度降一维度,使得
O(n^2^)的算法时间复杂度变为
O(n)
指针类型
- 对撞指针
- 快慢指针
对撞指针
- 一般是用于顺序结构中的,也可以称为左右指针,从两端向中间移动,最左、最右,向中间逐渐逼近。
- 对撞指针的结束条件一般为left==right 或者 left>right
快慢指针
- 基本思想为使用两个移动速度不同的指针在数组或者链表等序列结构上移动,比如在处理环形链表或者是数组时很有用
- 只要是我们研究的问题涉及到循环往复的情况,就可以考虑使用快慢指针的思想
- 快慢指针的实现方式有很多种,但是最为经典的是,在一次循环中,每一次让慢的指针移动一步,快的指针移动两步,如果成环(循环),那么两个指针会相遇
leetcode题目
题目链接 :移动零
c
class Solution {
public:
void moveZeroes(vector<int>& nums) {
//这是数组划分的题目,我们使用双指针的方式来解决
//cur=0 dest=-1 dest指向的是最后一个不为0的数
//移动cur,查找是否有nums[cur]!=0 那么 与nums[++dest] 交换,使得
//[0,dest] [dest+1,cur-1] [cur,n-1]
//数组分为上述三部分,第一部分为非零部分 第二部分为0部分,第三部分为未处理的部分
//我们将非零部分,找到之后就放在当前非零数组的最后一个元素之后的位置,这样就可以实现分块
for(int cur=0,dest=-1;cur<nums.size();cur++)
{
if(nums[cur])
{
//如果不为0
swap(nums[++dest],nums[cur]);
}
}
}
};
为什么是swap(nums[++dest],nums[cur]),这是因为我们规定的是dest位置是已处理区域的最后一个非零元素,我们发现在未处理区域cur之后,发现非零元素,将++dest,得到最后一个元素的后一个位置,进行交换,自然实现了0(已处理的零区域)与nums[cur]的移动
题目描述: 复写零
c
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
//对于数组的元素进行处理,我们使用双指针
//因为复写,遇到0要复写0,所以从前向后的双指针不行,我们使用从后向前指针,这样就不会造成数据被覆盖
//从后向前进行双指针,我们首先要确定的是开始判断是否复写的起始位置
//起始位置的确定,就是复写完毕之后的数组最后一个元素在原先数组中的下标位置
//我们使用双指针进行判断,找到确定的位置
//先找到最后一个位置
//找到位置之后,可能会有种可能nums[dest]=0 所以我们将这种可能避免
int cur=0,dest=-1; //cur比dest多1,实际上就是先++cur,然后进行判断
while(cur<arr.size())
{
if(arr[cur])
{
dest++;
}
else{
dest+=2;
}
if(dest>=arr.size()-1)
{
break;
}
cur++;
}
cout<<arr[cur]<<endl;
//处理边界问题,dest可能大于arr.size()-1
if(dest==arr.size())
{
arr[arr.size()-1] =0;
dest-=2;
cur--;
}
//开始复写 ,从后向前
while(cur>=0)
{
if(arr[cur])
{
//如果不为0
arr[dest]=arr[cur];
cur--;
dest--;
}
else
{
//为0 复写
arr[dest]=arr[cur];
arr[--dest]=arr[cur];
--dest;
--cur;
}
}
}
};
题目描述: 快乐数
c
class Solution {
public:
//得到每一位数字的平方和
int func(int n)
{
int ans=0;
while(n)
{
ans+=(n%10)*(n%10);
n=n/10;
}
return ans;
}
bool isHappy(int n) {
//根据题意所知,不管如何都会形成一个循环
//根据循环,我们知道之前判断循环链表的做法,快慢指针的形式
//slow和fast都是指向经过平方之后的数字,来代替指针
//slow走一步平方一次,fast平方两次
//实现平方的函数
cout<<func(100)<<endl;//检验func函数
int slow=n;
int fast=n;
while(1)
{
//直到相等才停止
slow=func(slow);
fast=func(fast);
fast=func(fast);
if(slow==fast)
{
break;
}
}
if(slow==1)
{
return true;
}else
{
return false;
}
//return true;
}
};
题目描述 :盛最多水的容器
c
class Solution {
public:
int maxArea(vector<int>& height) {
//使用双指针的算法来解决
//在两边设置指针,移动较小的哪一方指针,然后计算此时的体积,不断的移动,直到相遇
int left=0,right=height.size()-1,ans=0;
while(left<right)
{
//先得到此时的体积
int v=(right-left)*min(height[left],height[right]);
ans=max(v,ans);
if(height[right]>height[left])
{
left++;
}
else
{
right--;
}
}
return ans;
}
};
题目描述 :有效三角形的个数
c
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int ans=0;
for(int i=nums.size()-1;i;i--)
{
int target=nums[i];
int left=0,right=i-1;
while(left<right)
{
if(nums[left]+nums[right]>target)
{
//满足三角形
ans+=right-left;
right--;
//left++;
}
else
{
left++;
}
}
}
return ans;
}
};
利用数组元素的单调性,先确定一个元素,然后使用双指针,判断当前三者是否符合三角形,如果符合,根据单调性,left左边的所有都符合,所以ans+=right-left,然后向左移动right,如果不符合,那么向右移动left,一直判断,一直ans+=right-left 直到left<right 更换下一位c,最后循环操作,返回ans
题目描述 :和为s的两个数字
c
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//因为是 递增的数组
//我们利用双指针算法来解决该问题 利用单调性进行优化
vector<int> v;
int left=0,right=nums.size()-1;
while(left<right)
{
if(nums[left]+nums[right]<target)
{
left++; //说明当前最大值加上最小值还是小于target
}
else if(nums[left]+nums[right]== target)
{
v.push_back(nums[left]);
v.push_back(nums[right]);
return v;
}
else
{
right--;
}
}
return v;
}
};
题目概述 :三数之和
c
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
//进行排序,然使用双指针算法,先确定一个数字,然后移动另外两个指针进行加减
vector<vector<int>> vv;
for(int i=0;i<nums.size();)
{
if(nums[i]>0) break;
int left=i+1,right=nums.size()-1;
while(left<right)
{
int sum=nums[left]+nums[right];
int target=-nums[i];
if(sum==target)
{
vector<int> v;
v.push_back(nums[i]);
v.push_back(nums[left]);
v.push_back(nums[right]);
//找到一次之后,对于相邻重复的元素进行跨越
vv.push_back(v);
//去重操作 left 和 right
left++,right--;
while(left<right && nums[left] == nums[left-1]) left++;
while(left<right && nums[right] == nums[right+1]) right--;
//防止越界
}else if(sum>target)
{
right--;
}else
{
left++;
}
}
//去重操作 i
i++;
while(i<nums.size() && nums[i] == nums[i-1]) i++;
}
return vv;
}
};
题目描述 :四数之和
c
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
//先固定一个数a,然后剩余三个数字用三数之和 的内容来解决
sort(nums.begin(),nums.end());
vector<vector<int>> vv;
for(int i=0;i<nums.size();)
{
int a=nums[i];
for(int j=i+1;j<nums.size();)
{
//三数之和的内容
int num=target-a;
int left=j+1,right=nums.size()-1;
while(left<right)
{
int sum=nums[left]+nums[right];
long t=(long)num-nums[j];
if(sum>t)
{
right--;
}
else if(sum<t)
{
left++;
}
else
{
vv.push_back({nums[i],nums[j],nums[left],nums[right]});
//加入vector后,处理重复问题 left 和 right
left++, right--;
while(left<right && nums[left] == nums[left-1]) left++;
while(left<right && nums[right] == nums[right+1]) right--;
}
}
//去重处理 j
j++;
while(j<nums.size() && nums[j] == nums[j-1]) j++;
}
i++;
while(i<nums.size() && nums[i] == nums[i-1]) i++;
}
return vv;
}
};
总结:
双指针的使用场景有很多种,使用对撞指针还是快慢指针是根据题意分析的,如果成环我们就使用快慢指针,双指针的算法是根据暴力算法的优化得到的,通过省去不必要的迭代,来实现优化。