漫话算法:双指针(带你刷题版)

126 篇文章 0 订阅
95 篇文章 0 订阅
本文详细介绍了双指针算法在LeetCode中解决一系列数组相关问题的过程,包括移动零、复写零、快乐数、盛最多水的容器、有效三角形和查找总价格等题目,展示了如何运用状态法和主元思想,并通过实例演示了解决这些问题的关键步骤和技巧。
摘要由CSDN通过智能技术生成

双指针思想一般用在数组相关的部分题目中,这种题目有两个特点,一个是不允许开辟新的数组,只允许在原数组上进行操作,第二个是它常常与内部元素的移动相关,现在,我来带着大家刷些题来感受一下

1.283. 移动零 - 力扣(LeetCode)

引入方法:状态法:在理解题目时,需要总结出题解的状态,此题的状态:1.非0元素在左,0元素在右 2.相对顺序不变

引入思想:主元思想:在对题目进行操作时,需要首先将题目数据进行划分,比如这道题就可以分为0元素以及非0元素两部分,对不同部分进行操作的方法叫做主元法

开始做题了!主元思想:首先将0元素移到右边试一试,状态法:双指针,一个指针找0,一个指针找非0,如果找到则两者互换,那么,开整

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
while(left<right)
{
    while(nums[left]!=0&&left<right)
    {
        left++;
    }
    while(nums[right]==0&&right>left)
    {
        right--;
    }
    swap(nums[left],nums[right]);
    left++;
    right--;
}
    }
};

运行一下

观察一下报错,我们可以发现,这个方法已经实现了状态1,但是元素的相对位置被改变,且我们可以观察到这种改变无法在后期通过简单排序修正,所以此思路是错误的

所以,要尝试对非0元素进行操作了,把非0元素放在左边,开整

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
int left=0;
for(int right=0;right<nums.size();right++)
{
    if(nums[right]!=0)
    {
        swap(nums[left],nums[right]);
        left++;
    }
}
    }
};

过了

2.1089. 复写零 - 力扣(LeetCode)

分析题意:遇到0就重写一次,思路异常简单,遇到0就把下一个位置也赋值为0,后面的元素一次向后移动一个即可,在循环时注意元素的记录,那么开整

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
   int des = 0;
   int n = arr.size();
   while (des < n)
   {
       if (arr[des])
       {
           des++;
       }
       else
       {
           n--;
           int x = des + 1;
           int sign = -10086;
           for (x; x < arr.size()-1; x++)
           {
               sign = arr[x+1];
               if(sign==-10086)
                arr[x+1]=arr[x];
                else
                arr[x+1]=sign;
           }
           arr[des] = 0;
           des++;
           arr[des] = 0;
           
           
       }
   }

    }
};

通过用例分析一下失败的原因:根本没有控制好字符串的长度导致了多次重写,所以我们要先把字符串的长度确定下来,以免以后出现问题 

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count!=arr.size())
{
    move++;
    count++;
    if(arr[move]==0)
    {
        count++;
    }
}

           
 

此时move指针的长度正好是要操作字符串的长度,那么再想一想,此时的count=arr.size(),而move也比正常的要多移动一次,所以让它们重新指向相应的位置

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count!=arr.size())
{
    move++;
    count++;
    if(arr[move]==0)
    {
        count++;
    }
}
count--;
move--;

那么有的兄弟就要问了,如果最后move指向的元素为0怎么办,那么字符串的最后只能输出一个0了,观察一下此时两指针的位置,发现count是n+1而不是n,那么就很简单了,覆写0的时候这种情况就不用覆写了

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count<arr.size())
{
        if(arr[move]==0)
    {
        count++;
    }
    move++;
    count++;
    
}
count--;
move--;
while(move>=0)
{
  if(count<arr.size())
  {
    arr[count]=arr[move];
  }
  if(arr[move]==0)
  {
    count--;
    arr[count]=0;
  }
  count--;
  move--;
}
           
 

    }
};

过了

3.202. 快乐数 - 力扣(LeetCode)

分析题意:当计算时只会出现两种情况,一种是无限循环,一种是得到1,对用例进行纸上模拟会发现,用例不是快乐数的原因是它中间会出现某一个值与前面的值相同,但这个值不为1,从而形成环进行无限循环,之前我们做过一道判断环形链表的题,得出判断环形链表的方法为设置快慢指针观察它们是否相遇,在此题的背景下,一定会相遇(这是一个结论),所以就定义快慢两个指针,一个一步进行两次操作,一个一步进行一次操作,然后对相遇后的值进行判断即可,需要注意的是我们把他们俩的初始值赋值为n,所以要使用do while循环让它们先进入到循环中

class Solution {
public:
    int conclude(int n)
    {
        int sum=0;
        while(n!=0)
        {
            sum+=(n%10)*(n%10);
            n=n/10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int fast=n;
        int slow=n;
        do
        {
             slow=conclude(slow);
            fast=conclude(conclude(fast));
           
        }while(fast!=slow);
   return slow==1;
    }
};

4.11. 盛最多水的容器 - 力扣(LeetCode)

算法思想:左右两个指针依次往内部移动,如果相遇则停止,每一步都计算体积并实时更新,移动方法:移动当前值最小的指针,因为此时内部的数据是固定的,如果容器的宽减小的情况下,容器的长要尽可能长才有超越原来体积的机会,所以我们此时选择让短边的指针移动

class Solution {
public:
    int maxArea(vector<int>& height) {
int left=0;
int right=height.size()-1;
int sum=0;
while(left<right)
{
sum=max(sum,(right-left)*min(height[left],height[right]));
height[left]<height[right]?left++:right--;
}
return sum;
    }
};

5.611. 有效三角形的个数 - 力扣(LeetCode)

将数组进行排序,然后利用三角形两边大于第三边的性质进行判断即可,但是很遗憾,超时了

使用双指针进行优化,对以每个数为长边的三角形进行判断,并且减少循环判断次数

class Solution {
public:
bool check(int i,int j,int k,vector<int>&nums)
{
    if(nums[i]+nums[j]>nums[k])
    {
        return true;
    }
    return false;
}
    int triangleNumber(vector<int>& nums) {
        int count=0;
        sort(nums.begin(),nums.end());
for(int i=nums.size()-1;i>=2;i--)
{
int left=0;
int right=i-1;
while(left<right)
{if(check(left,right,i,nums))
{
count+=(right-left);
right--;
}
else
{
    left++;
}
}
}
return count;
    }
};

过了

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

以前面的刷题经验,一眼发现了有序这个条件,根据上一题,可以直接写出双指针代码

class Solution {
public:
 vector<int>ret;
    vector<int> twoSum(vector<int>& price, int target) {
ret=vector<int>(2,-1);
 int left=0;
 int right=price.size()-1;
 while(left<right)
 {
    if(price[left]+price[right]==target)
    {
        ret[0]=price[left];
        ret[1]=price[right];
        return ret;
    }
    if(price[left]+price[right]>target)
    {
        right--;
    }
    else
    {
        left++;
    }
 }
 return  ret;

    }
};

7.15. 三数之和 - 力扣(LeetCode)

这道题也是双指针,但有很多细节问题需要处理,第一个就是需要进行去重,一种方法是使用set来去重,另一种方法是在循环中进行去重,在遇到相同值时跳过,跳过要在两步,一步是找到之后,一个是完成双指针算法之后,为了不漏,所以在找到结果后不停止循环

class Solution {
public:



    vector<vector<int>> threeSum(vector<int>& nums) {
       
       vector<vector<int>>ret; 
       sort(nums.begin(),nums.end());

        for(int d=0;d<nums.size();)
        {
           if(nums[d]>0)
           {
            break;
           }
            int left=d+1;
            int right=nums.size()-1;
            while(left<right)
            {
                  if(nums[left]+nums[right]+nums[d]>0)
                {
                    right--;
                }
                else if(nums[left]+nums[right]+nums[d]<0){
                    left++;
                }
                else
                {
                   
                      ret.push_back({nums[left],nums[right],nums[d]});
                    left++;
                    right--;
                       while (left < right && nums[left] == nums[left - 1]) ++left;   
                    while (left < right && nums[right] == nums[right +1]) --right; 
              
                }
             
            }
            d++;
            while(d<nums.size()&&nums[d-1]==nums[d])
            {
                d++;
            }
        }
        return ret;
    }
};

8.18. 四数之和 - 力扣(LeetCode)

和三树之和类似首先判断极限情况,就是当四个数分别位于倒数第四三二一的时候,从而确定四个数的边界,由此可以对三数之和的代码进行改改造,把外层固定一个数的for循环换成两层嵌套的for循环,然后在第二层for循环的内部再进行双指针操作,直接复制三数之和的代码并微调即可

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, long long target) {
sort(nums.begin(),nums.end());
vector<vector<int>>ret;
if(nums.size()<4)
{
    return ret;
}
for(long int num1=0;num1<nums.size()-3;num1++)
{
if(num1>0&&nums[num1]==nums[num1-1])
{
    continue;
}
for(long int num2=num1+1;num2<nums.size()-2;)
{
    if(num2>num1+1&&nums[num2]==nums[num2-1])
    {
        continue;
    }
   long int left=num2+1;
   long int right=nums.size()-1;
  while(left<right)
            {
                  if(nums[left]+nums[right]+nums[num1]+nums[num2]>target)
                {
                    right--;
                }
                else if(nums[left]+nums[right]+nums[num1]+nums[num2]<target){
                    left++;
                }
                else
                {
                   
                      ret.push_back({nums[left],nums[right],nums[num1],nums[num2]});
                    
                       while (left < right && nums[left] == nums[left +1]) ++left;   
                    while (left < right && nums[right] == nums[right -1]) --right; 
              left++;
                    right--;
                }
             
            }
            num2++;
            while(num2<nums.size()&&nums[num2-1]==nums[num2])
            {
                num2++;
            }
}
}
return ret;
    }
};
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, long long target) {
sort(nums.begin(),nums.end());
vector<vector<int>>ret;
if(nums.size()<4)
{
    return ret;
}
for(long int num1=0;num1<nums.size()-3;num1++)
{
if(num1>0&&nums[num1]==nums[num1-1])
{
    continue;
}
for(long int num2=num1+1;num2<nums.size()-2;)
{
    if(num2>num1+1&&nums[num2]==nums[num2-1])
    {
        continue;
    }
   long int left=num2+1;
   long int right=nums.size()-1;
  while(left<right)
            {
                  if(nums[left]+nums[right]+nums[num1]+nums[num2]>target)
                {
                    right--;
                }
                else if(nums[left]+nums[right]+nums[num1]+nums[num2]<target){
                    left++;
                }
                else
                {
                   
                      ret.push_back({nums[left],nums[right],nums[num1],nums[num2]});
                    
                       while (left < right && nums[left] == nums[left +1]) ++left;   
                    while (left < right && nums[right] == nums[right -1]) --right; 
              left++;
                    right--;
                }
             
            }
            num2++;
            while(num2<nums.size()&&nums[num2-1]==nums[num2])
            {
                num2++;
            }
}
}
return ret;
    }
};

过了吗?过不了一点

笔者很好奇,此题不管有什么方法,都会用到起码四个位置的加法比较,笔者不知道为什么会传入这么大的几个数,这样加法是必然过不了的,除非这题也要使用大数加法的计算,而这种题要搞这种是很无语的,日前力扣更新了一下,避免使用long sum存储结果导致逃避大数加法的情况,服

9.来总结一下双指针吧:双指针的应用场景:数组或字符串进行操作而不使用额外空间的,使用判断环的方法的,明显左右两侧同时操作的,在数组中执行各种运算的

双指针的编写前提:在纸上复现用例的形成过程

双指针的知识储备:熟记移动零,复写零,三数之和三个经典模板

好啦,就到这里了,拜拜亲宝们

  • 35
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值