双指针算法例题

目录

移动零

题目描述

思路

代码

复合零

题目描述

思路

代码

快乐数

题目描述

思路

代码

盛最多水的容器

题目描述

​编辑

思路

代码

有效三角形个数

题目描述

思路

代码

三数之和

题目描述

思路

代码

四数之和

题目描述

思路

代码


双指针,用于数组中的划分问题

  • 说是指针,但指针的定义没那么严格
  • 可以是下标,可以是其他字段,只要可以标识某位置即可

移动零

题目描述

意思是 -- 将数组分为两部分,非0和0

  • 非0,需要保证其顺序仍为原数组中的顺序

思路

很明显的数组划分,所以:

  • 我们一个指针cur用来遍历数组(<cur是已处理的,>=cur还未处理)
  • 一个指针pos用来区分非0和0(可以让他指向最后一个非0数,也可以指向第一个0)

代码

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        //指向第一个0
        int pos=0,cur=0;
        for(auto it:nums){
            if(it!=0){
                swap(nums[pos],nums[cur]);
                ++pos;
            }
            ++cur;
        }

        //指向最后一个非0
        int pos=-1,cur=0;
        for(auto it:nums){
            if(it!=0){
                ++pos;
                swap(nums[pos],nums[cur]);
            }
            ++cur;
        }
    }
};

无论如何,都是要将[指向非0数的cur]与[第一个0]交换

  • 所以pos的++操作顺序不同

因为已经处理的数字分为两部分:[非0][0],所以:

  • 如果第一个数为0,指向0的pos=0,指向非0的pos=-1
  • 如果非0,指向0的pos=1,指向非0的pos=0
  • 所以可以看出来,他俩的初始值分别是0和-1(因为之后都要执行++操作,所以取最小值为他们的初始值)

复合零

题目描述

  • 按照遍历数组的顺序,非0数不变,遇到0要写2遍
  • 不能越界,写到数组结尾就结束
  • 不能创建新的数组

思路

原地从左到右遍历数组的话,会覆盖掉后面的数字

但是也不能完全从右向左遍历,因为不是所有的数字都会在新数组里

  • 所以,我们可以考虑先从左到右,找到新数组的最后一个数字
  • 然后从该数字开始,从右往左进行复写
  • 这样既可以保证数字不被覆盖,也可以保证数字的顺序不变

也就是说,其中一个指针cur用于遍历数组,另一个指针pos用于指示当前需要被复写的数字

注意

  • 如果是0000000,数字长度是奇数,但由于0复写2次,遍历时一定会让cur在结尾处越界一个单位
  • 那么复写时,就会让cur在起始处越界一个单位
  • 所以,复写前需要单独处理一下 -- 把那个导致越界的0手动操作一下,不能让他们进入处理普通情况的复写代码

代码

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int pos=0,cur=-1;
        int n=arr.size();
        while(true){ 
            if(arr[pos]==0){
                ++cur;
            }
            ++cur;
            if(cur>=n-1){ //因为0要赋值两次,所以会出现cur移动到n的位置
                break;
            }
            ++pos;
        }
        if(cur==n){ //处理超范围的情况
            --cur; //首先将cur指向有效的最后一个数字
            arr[cur]=0; //因为只有可能是0导致cur加了两次
            --cur;--pos;//cur和pos指向下一个未处理的数字
        }
        while(true){
            arr[cur]=arr[pos];//当前位置直接赋值
            if(arr[pos]==0){ //是0就多赋值一次
                arr[--cur]=0;
            }
            --pos; //pos指向下一个位置
            if(cur==0){ //如果此时已经到头,说明已经处理完毕,结束
                break;
            }
            --cur; //指向下一个未处理的数字
        }
    }
};

快乐数

题目描述

  • 给出一个数字n,会得到一串数字
  • 在这串数字上,下一个数字=上一个数字每一位的平方和(eg:19 -> 1^2 +9^2=82)
  • 如果最终这个平方和可以=1,就是快乐数

思路

如果是快乐数,说明它的变化情况如图(因为1的平方和=1):

如果不是快乐数,最终也会成环

  • 因为
  • 如果把每一位都用9替代->9999999999(有10个9),它的平方和=10*9^2=810
  • 所以,n的最大平方和不会超过810,说明无论起始值有多大,只要进行运算一次,就不会超过810
  • 所以,只要循环下去,必定成环(因为平方和的范围不是无限大的)
  • 所以,它的变化情况如图:

所以,我们只要判断环内数字是否=1即可

是不是很像我们的链表成环,只不过我们不需要判断是否成环,也不需要找到开始成环的第一个结点

  • 当时我们用的快慢指针,只要两指针相遇,就说明有环

这里也可用类似的思想,只不过判断的是相遇时他俩值是否等于1

代码

class Solution {
public:
    int func(int n){
        int res=0;
        while(n){
            int t=n%10;
            res+=(t*t);
            n/=10;
        }
        return res;
    }
    bool isHappy(int n) {
        int fast=n,slow=n;
        while(true){
            slow=func(slow);
            fast=func(fast);
            fast=func(fast);
            if(slow==fast){
                if(slow==1){
                    return true;
                }
                return false;
            }
        }
    }
};

盛最多水的容器

题目描述

  • 水的容积=宽(两条高的下标之差)*高(数组中的值)
  • 从数组中选出任意两个值,找到容积最大值

思路

我们考虑从外向内找

  • 从内向外找的话,容易漏,具体从哪开始也是个问题

所以,考虑到宽在减小(两条高在向内移动)

  • 必须让高变大,才有可能让容积变大;否则一定是变小
  • 所以每次向内探索,都让短边移动,试图找到更长的边
  • 最后,每次得到一个容积都和最大值进行比较

代码

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

有效三角形个数

题目描述

  • 判断三角形的条件 -- 短边之和大于最大边
  • 注意:重复边也是有效的

思路

因为需要判断短边之和大于最大边

  • 那么我们可以每次首先取极值来判断(不然就是全部遍历一遍,妥妥超时)
  • 首先需要排序
  • 然后每次将最大值视为最大边c
  • 然后,取出另外一部分数字的最大/小值,作为短边a,b(也就是双指针left,right)
  • 如果a+b>c,那么中间的数字就不需要判断了,他们中的任意一个+b一定>c
  • 所以,当前的b就可以统计次数了,然后舍弃掉当前的b,将它左边的一个数作为b,进行下一轮比较
  • 如果a+b<=c,说明此时的a不够大,舍弃掉它,将下一个数作为a进行比较

  • 当a和b相遇时,就说明以当前的c为最大边的情况已经遍历完毕
  • 所以,c左移一个单位

  • 重复上述过程,直到凑不出三角形为止

代码

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

三数之和

题目描述

  • 三元组之间不可重复

思路

和上一道题的操作类似,要求某个特定的和,先排序一定能减少我们的操作

然后确定一个数作为基准数,在剩下的部分找另外两个数

  • 如果和=(-基准数),他们组成的三元组之和=0
  • 两个数遍历的时候,如果加起来<基准数,左指针就向右移动
  • >基准数,右指针就向左移动

而且三元组之间不可以重复

  • 那么当我们的指针在移动时,就需要跳过重复的数字(如果不跳过的话,相同的数字会找到相同的组合)

且这里的三个指针都需要去重:

  • 基准数如果相同,在剩下数字里找的时候,还是会找到原来的组合
  • 双指针a,b在遍历的时候,如果不去重 : 基准数不变,a不变,那么对应的就是特定的b,如果b不去重,就会出现重复的三元组 ; 反过来也是一样

当然,去重的时候因为要移动指针,就很可能出现越界访问

  • 比如:0,0,0,0这个例子,一旦去重,就会越界
  • 所以需要加上边界控制

注意,剩下的部分里,可能不只有一组三元组符合我们的要求

  • 所以我们必须全部遍历完,再找下一轮

代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        int n=nums.size();
        sort(nums.begin(),nums.end());
        //固定数字
        int c=n-1;

        while(c>=2){
            int a=0,b=c-1;
            int comp=-nums[c];
            while(a<b){
                if(nums[a]+nums[b]>comp){
                    --b;                   
                }
                else if(nums[a]+nums[b]<comp){
                    ++a;                
                }
                else{
                    res.push_back({nums[a],nums[b],nums[c]});
                    --b;++a;    
                    //去重
                    while(a<b&&nums[b]==nums[b+1]){
                        --b;
                    }   
                    while(a<b&&nums[a]==nums[a-1]){
                        ++a;
                    }           
                }
            }
            --c;
            //固定数也需要去重
            while(c>=2&&nums[c]==nums[c+1]){
                --c;
            }
        }
        return res;
    }
};

四数之和

题目描述

  • 和三数之和的要求基本一致(不重复)

思路

比三数之和多了一个数字

  • 也就是多了一层循环
  • 相当于,每次固定一个数d,然后在剩下的区间里进行三数之和的逻辑
  • 而三数之和的逻辑 -- 每次固定一个数c,然后在剩下的区间里,用双指针遍历,直到指针相遇

同样,也需要去重,只不过比三数之和多了一次去重

  • 首先双指针需要去重
  • 然后是固定数c
  • 最后是固定数d

代码

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        if(nums.size()<4){
            return res;
        }
        sort(nums.begin(),nums.end());

        int d=nums.size()-1;
        while(d>=3){ //固定数d
            //三数之和的逻辑:
            int c=d-1;
            while(c>=2){ //固定数c
                long long  comp2=(long long)target-nums[d]-nums[c];
                //会有target很小,而数组中的值很大的情况,相减会溢出,所以转为ll

                int a=0,b=c-1; //双指针a,b
                while(a<b){
                    long long sum=nums[a]+nums[b];
                    if(sum<comp2){
                        ++a;
                    }
                    else if(sum>comp2){
                        --b;
                    }
                    else{
                        res.push_back({nums[a],nums[b],nums[c],nums[d]});
                        ++a;--b;
                        //双指针去重
                        while(a<b&&nums[a]==nums[a-1]){
                            ++a;
                        }
                        while(a<b&&nums[b]==nums[b+1]){
                            --b;
                        }
                    }
                }
                //固定数c去重
                --c;
                while(c>=2&&nums[c]==nums[c+1]){
                    --c;
                }
            }
            //固定数d去重
            --d;
            while(d>=3&&nums[d]==nums[d+1]){
                --d;
            }
        }
        return res;
    }
};
  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值