3-26 双指针算法

双指针

先说说双指针用来干嘛?

  1. 就是类似于快排的代码:i,j将一个数组分成三份已经处理好的,未处理好的,已经处理好的三份[{0,…,i},{i+1,…,j-1},{j,l-1}]
    1. 同样,我们可以分成两份,已经符合条件的,i(作为标签,说明i-1以前的都是处理好的),i到j的都是判定不符合条件的,需要被处理的,这个需要j遇到再处理[{0,…,i},{j,l-1}]
  2. 就是通过双指针来降低时间复杂度。
    1. 比如接雨水问题:通过贪心,然后用双指针来解决。
  3. 双指针查询问题:一般查询暴力的话都至少需要O(ne2),通过排序或者通过其他方式可以很好的处理查询的问题。
  4. 当然还有大名鼎鼎的滑动窗口,这个时候也需要i,j来控制滑动窗口
    上面是做的简单的总结:下面看具体的代码

1. 双指针模仿栈

题目:27移除元素
示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]

我们可以知道,这题也是将数组分成两部分,一部分是不为3的,一部分是属于3而且又是操作原数组。
这个时候我们可以直接改写快排,非常方便,直接让flag=val即可,但是这个过程需要递归操作。
我们可以想一下,能不能不递归就在原来数组上修改,其实也就是一个sort的过程,分成{1,23,…,|val,val}

int removeElement(vector<int>& nums, int val) {
    int i=0,j=0;
    while(j<nums.size())
    {
        if(nums[j]!=val)
        {
            swap(nums[i],nums[j]);
            i++;
        }
        j++;
    }
    return i;
}
int main()
{
    vector<int>v={3,2,2,3};
    int i=removeElement(v,3);
    cout<<"sort end:"<<endl;
    for(auto N:v)
    {
        cout<<N<<"_";
    }
    cout<<"end:"<<endl;
    return 0;
}

2. 26删除有序数组中重复的数字

#include<iostream>
#include<vector>
using namespace std;

int removeDuplicates(vector<int>& nums) { 
    int i=0,j=0;
    while(j<nums.size())
    {
        if(j>0&&nums[j]!=nums[j-1])
        {
            i++; //1. nums[0]是不处理的
            nums[i]=nums[j];//2. 直接覆盖,然后可以保证0,0,1,1,1不会因为交换函数出现0,1,0,1,1的问题
        }
        j++;
    }
    return i;
}
int main()
{
    vector<int>v={0,0,1,1,1,2,2,3,3,4};
    int i=removeDuplicates(v);
    cout<<"sort end:"<<endl;
    for(auto N:v)
    {
        cout<<N<<"_";
    }
    cout<<"end:"<<endl;
    return 0;
}

双指针快排,递归

int partition(vector<int>&nums,int l,int r){
    int i=l,j=l;
    int t=l+rand()%(r-l+1);
    swap(nums[r],nums[t]);
    int pivot=nums[r];
    while(j<r){
        if(nums[j]<=pivot)
        {
            swap(nums[i],nums[j]);
            i++; //第一个元素也是需要进行排序的,所以i++放到后面
        }
        j++;
    }
    swap(nums[i],nums[r]);
    return i;
}

void quickSort(vector<int>&nums,int l,int r)
{
    if(l>=r)return;
    int pivot=partition(nums,l,r);
    quickSort(nums,l,pivot-1);
    quickSort(nums,pivot+1,r);
}


int main()
{
    srand(time(0));
    vector<int>v={10,9,12,3,6,78,69,15,22,19};
    int l=v.size();

    quickSort(v,0,l-1);
    cout<<"sort end:"<<endl;
    for(auto N:v)
    {
        cout<<N<<"_";
    }
    cout<<"end:"<<endl;
    return 0;
}

简单对比一下快排和这个模拟栈:
swap的区别

快排
 while(j<r){
        if(nums[j]<=pivot)
        {
            swap(nums[i],nums[j]);
            i++; //第一个元素也是需要进行排序的,所以i++放到后面
        }
        j++;
    }

模拟栈
while(j<nums.size())
    {
        if(nums[j]!=val)
        {
            swap(nums[i],nums[j]);
            i++; //第一个元素也是需要进行考虑的,所以i++放到后面
        }
        j++;
    }

删除重复元素
 while(j<nums.size())
    {
        if(j>0&&nums[j]!=nums[j-1])
        {
            i++; //1. nums[0]是不处理的
            nums[i]=nums[j];//2. 直接覆盖,然后可以保证0,0,1,1,1不会因为交换函数出现0,1,0,1,1的问题
        }
        j++;
    }

思考总结1

我们是不是可以从上面发现规律,或者代码的框架了
当我们需要将数组分割成两个不同规律的区域时,我们可以进行如下处理:

 int i=l,j=l;//左边界的开始,都是0,也可以将i赋值为-1,看个人习惯
 while(j<nums.size())//下标不能超过数组最大值
    {
        if(**具体的处理逻辑**)//2.当nums[j]满足条件的就将下标为i值赋值或者交换为j的值
        {
            1.i=0是不是要处理,如果不处理,则先让i++;
            i++; 
            nums[i]=nums[j];
            2.快排时,j=0也要判断是不是把他放到{0,i}这个区间内
            {
                swap(nums[i],nums[j]);
            i++; //第一个元素也是需要进行考虑的,所以i++放到后面
            }
            
        }
        j++;
    }

容积问题

11盛最多睡的容器

  1. 问题描述
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
  1. 思路
    1. 我们开始的思路可以这么想,要求盛最多的容器,就是容器内每一个相邻数组比如[i,i+1]的值最大,我们可以用贪心算法来解决。同时需要考虑到容器内的个数。
    2. 而容器内相邻数组最大的话,我们可以找出左右两边heigh[x],height[y]两个分别最大,以第三个{8,,6}能够小格子能够盛最多水,为左边为8,右边为8的时候,这个时候最大为8,但这个只是局部最优解,要求全局最优解,需要考虑容器内的个数所以,我们可以推导出全局最优解globl_max=min(小格子左边线的最长长度,小格子右边线的最长长度)*容器内的个数(x-y)。
    3. 我们所以这个时候我们直接暴力求解,左边的将0-1小格子考虑进去的话,从左边寻找最大的线长度i=0(本身),右边最大线长度从j=height.size()-1。
暴力解
#include<iostream>
#include<vector>
using namespace std;

//1.暴力解
int maxArea(vector<int>& height) {
    int l=height.size();
    int res=0;
    vector<int>lv(l),rv(l);
    int lm=0;
    int rm=0;
    for(int i=0;i<l;++i)
    {
        lm=max(lm,height[i]);
        lv.push_back(lm);
    }
    for(int i=l-1;i>=0;--i)
    {
        rm=max(rm,height[i]);
        rv.push_back(lm);
    }
    // for(int i=0;i<l;++i)
    // {
    //     int tmp=0;
    //     lm=lv[i];
    //     rm=rv[i];
    //     tmp=min(lm,rm)*
    //     res=max(res,tmp);
    // }
    int left=0,,right=l-1;
    while(left<right)
    {
        int tmp=min(lv[left],rv[right])*(right-left);
        if(lv[left]<=rv[right])
        {
            left++;
        }else{
            right++;
        }
        res=max(res,tmp);
    }
    return res;
}
int main()
{
    vector<int>v={1,8,6,2,5,4,8,3,7};
    int res=maxArea(v);
    cout<<"sort "<<endl;
    cout<<res<<endl;
    cout<<"end:"<<endl;
    return 0;
}

时间复杂度为O(1),空间复杂度为O(1)

用空间换时间的思路
#include<iostream>
#include<vector>
using namespace std;
//备忘录方式
int maxArea(vector<int>& height) {
    int l=height.size();
    int res=0;
    vector<int>lv(l),rv(l);//注意这里如果用l进行初始,下面的lv[i]=lm,rv[i]=rm;这个时候用的是双指针,一个增一个减
    int lm=0;
    int rm=0;
    for(int i=0;i<l;++i)
    {
        lm=max(lm,height[i]);
        lv[i]=lm;
    }
    for(int i=l-1;i>=0;--i)
    {
        rm=max(rm,height[i]);
        rv[i]=rm;
    }
    int left=0,right=l-1;//因为是lm[1,..,left],rm[right,...,l-1]
    while(left<right)
    {
        int tmp=min(lv[left],rv[right])*(right-left);
        if(lv[left]<=rv[right])
        {
            left++;
        }else{
            right--;
        }
        res=max(res,tmp);
    }
    return res;
}


int main()
{
    vector<int>v={1,8,6,2,5,4,8,3,7};
    int res=maxArea(v);
    cout<<"sort "<<endl;
    cout<<res<<endl;
    cout<<"end:"<<endl;
    return 0;
}

代码稍微修改一下:

int maxArea(vector<int>& height) {
    int l=height.size();
    int res=0;
    vector<int>lv,rv;
    int lm=0;
    int rm=0;
    for(int i=0;i<l;++i)
    {
        lm=max(lm,height[i]);
        lv.push_back(lm);
    }
    for(int i=l-1;i>=0;--i)
    {
        rm=max(rm,height[i]);
        rv.push_back(rm);
    }
    for(int i=0;i<l;i++)
    {
        tmp=min(lv[i],rv[i])*
    }
    return res;
}

如果用lv[1]表示1左边最高,rv[i]为右边最高的话,这个时候无法获取i到两个最高边界的距离,所以如果想用的话,必须用一个vector<pair<int,int>>来表示,一个是边界长度,一个是边界距离
所以双指针这个时候的优势出来了。

双指针不用备忘录
#include<iostream>
#include<vector>
using namespace std;

//1.暴力解
int maxArea(vector<int>& height) {
    int l=height.size();
    int res=0;
    int i=0,j=l-1;
    int lm=0,rm=0;
    while(i<j)
    {
        lm=max(height[i],lm);
        rm=max(height[j],rm);
        int tmp=min(lm,rm)*(j-i);
        res=max(tmp,res);
        if(lm<=rm)
        {
            i++;
        }else{
            j--;
        }
    }
    return res;
}
int main()
{
    vector<int>v={1,8,6,2,5,4,8,3,7};
    int res=maxArea(v);
    cout<<"sort "<<endl;
    cout<<res<<endl;
    cout<<"end:"<<endl;
    return 0;
}

总结:双指针都出现了下面的代码

if(lm<=rm)
{
    i++;
}else{
        j--;
}
  1. 我们前面讲过用的是贪心算法,第一个小格子矩形,能够称多少水取决于左右的min(lm,rm),可以想象成木桶原理,这个时候我们肯定是去找补短板,然后去找一个更加长的。
  2. 数学推导怎么推导?s=min(x,y)*(yi-xi)
    1. 反推法:如果我们保持x边界不变化,这个时候min(x,m(未来的边界))<=m,同时(yi-xi)一定是变小的,双指针靠拢了,所以这个时候可以确定min(x,y)*(yi-xi)就是最大值了。
    2. 如果保持y边界不变,还有可能让min(y,m(未来的边界))>=y,s虽然这个时候(yi-xi)变小了

42 接雨水

#include<iostream>
#include<vector>
using namespace std;
int trap(vector<int>& height) {
    int l=0;
    int r=height.size()-1;
    int lm=0,rm=0;
    int res=0;
    while(l<r)
    {
        lm=max(lm,height[l]);
        rm=max(rm,height[r]);

        //这里不再是容器问题,而是问矩形上面还能接多少雨水,所以他是求局部最优解,然后相加就能得到全局最优解,
        所以该矩形只和左右矩形边界,以及自身高度有关,如果左边更高的话,漏桶原理,智能找右边,并得到能盛的雨水
        if(lm<rm)
        {

            res+=lm-height[l];
            l++;
        }else{
            res+=rm-height[r];
            r--;
        }
    }
    return res;
}

int main()
{
    vector<int>v={0,1,0,2,1,0,1,3,2,1,2,1};
    int res=trap(v);
    cout<<"sort "<<endl;
    cout<<res<<endl;
    cout<<"end:"<<endl;
    return 0;
}

        所以该矩形只和左右矩形边界,以及自身高度有关,如果左边更高的话,漏桶原理,智能找右边,并得到能盛的雨水
        if(lm<rm)
        {

            res+=lm-height[l];
            l++;
        }else{
            res+=rm-height[r];
            r--;
        }
    }
    return res;
}

int main()
{
    vector<int>v={0,1,0,2,1,0,1,3,2,1,2,1};
    int res=trap(v);
    cout<<"sort "<<endl;
    cout<<res<<endl;
    cout<<"end:"<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值