第三课:双指针、位运算、离散化、区间合并

目录

一、双指针

双指针题目1:

 双指针题目2:

 双指针题目3:

二、位运算

四、离散化

 五、区间合并


一、双指针

两个指针维护一段区间,可以把暴力算法的O(n^2)复杂度优化到O(n)

使用双指针要确保指针要具有某种性质(看每道题的具体逻辑,比如单调性:j永远大于等于i)。

双指针题目1:

题目目标:找到最长的一段不重复的连续子序列长度。

分析:用双指针维护这个数组的一段区间,使这个区间内无重复元素。长度即为i-j+1,

用res存储最大的长度,当我们遇到重复数字,要调整双指针区间的时候,令新res=max(res,i-j+1).

思路:开一个新数组s用来记录双指针区间内数字出现的次数,当一个新数字进入双指针区间时s[a[i]]++,当pop出双指针区间时,s[a[j]]--。 

//这里填你的代码^^
#include<iostream>
using namespace std;
const int N=100010;
int a[N],s[N];//开了个数组计数

int main()
{
    int n;
    int res=0;
    cin>>n;
    for(int i=0;i<n;i++)  cin>>a[i];

    for(int i=0,j=0;i<n;i++)
    {
        s[a[i]]++;
        while(s[a[i]]>1&&j<=i)//新进来的数重复了
        {
            s[a[j]]--;
            j++;
        }
        res=max(res,i-j+1);//此时j走到能走的最远段,,对每个i都会更新一次
    }
    cout<<res;
    return 0;

}
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/3367697/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 双指针题目2:

 目标:在a,b数组中找到两个下标,使得a[i]+b[j]=x;

分析:用l,r作为a,b中指示a,b中下标。一个要遍历整个数组,另一个根据情况往前或往后。为了方便起见,我们不能让两个指针都从0开始,这样很难让l,r 变化。我们可以让l从末尾开始,r从0开始,根据数组已经排序的特性,对于每一个i,r从0开始找到一个最小的大于等于目标值得相加值,若为大于,则l--。这样比较方便。

代码如下

//这里填你的代码^^
#include<iostream>
using namespace std;
const int N=100010;
int n,m,x;
int a[N],b[N];
int main()
{
    cin>>n>>m>>x;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<m;i++) cin>>b[i];
    int l,r;//l,r分别为a b 数组里面的指针 让l从最后开始 r从最前开始
    for(l=n-1,r=0;l>=0;l--)//对每一个l 尝试找一个r作为解
    {
        if(a[l]+b[r]==x)
        {
            cout<<l<<" "<<r<<endl;break;
        }
        while(a[l]+b[r]>x)
        {
            if(r>0) r--;
            else break;
        }
        while(a[l]+b[r]<x)
        {
            if(r<m-1) r++;
            else break;
        }
        if(a[l]+b[r]==x) cout<<l<<" "<<r<<endl;
        else continue;
    }
    return 0; 
}
/*思路:对每一个l  如果此时l,r满足条件,则输出;若大于x,则让r往左移动(因为已经确定了升序)
直到计算值小于等于目标值x;若小于x,则r++直到计算值大于等于x;(注意判断r是否已经走到尽头!)
最后判断一下这个计算值到底是不是x(因为有可能大于,或者小于),如果是的话就输出,不是的话就i--进入下一次循环了
不过编写此代码的时候没有考虑解的唯一性,目测是有更好的算法的。*/
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/3375166/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 双指针题目3:

 思路:l指针从0开始指示a中每一个数,r指针从0开始指示b中的数。为满足要求,需要对每一个l,都找到一个r符合要求,并且r只能往后走不能往前走。如果全找到了,则是子序列,反之则不是。

边界问题:1.r++的时候不能越界 2.当最后一次循环结束时,要在循环体内判断结果。3.r++之后要判断是否找完了或者越界了。

实现过程:

//这里填你的代码^^
#include<iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
int n,m;

int main()
{
    cin >>n>>m;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<m;i++) cin>>b[i];

    int l,r;//l r分别是a b数组中的指针
    for(l=0,r=0;l<n;l++)
    {
        while(a[l]!=b[r]&&r<m)
        {
            r++;
        }
        if(r>=m){cout<<"No"; break;}
        if(a[l]==b[r]) r++;        
        if(l==n-1) {cout<<"Yes";break;}
        if(r>=m){cout<<"No"; break;}




    }
    return 0;
}
/*思路:从a[0]开始,在r不越界的条件下一直r++找到a[l]=b[r];出了循环有两种情况,r越界了,输出NO。r没越界
说明找到了,那么r++。此时又有两种情况,r此时越界了但没找完a中所有元素,输出NO。r无所谓越不越界但是找完了
输出Yes*/
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/3375314/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二、位运算

这部分讲得好少,浅浅记录一下。

 需要一个lowbit函数,输出x在二进制表示下,从各位开始第一个带有1的位数及之前的所有0,譬如10的二进制表示为1010,则lowbit(10)=10,譬如某数二进制表示101000,则lowbit为1000。

lowbit函数实现用到了位运算以及数的补码:

 每次x减去lowbit(x),就把下一个1之前的数全部变成0,,譬如1010010-lowbit=1010000,再减其lowbit则等于1000000。减最后一次变成0,减了几次变成0说明有几个1。

实现过程:

#include<iostream>
using namespace std;
int lowbit(int x)
{
    return x&-x;
}
int main()
{
    int n,res=0,x;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        res=0;
        cin>>x;
        while(x) x-=lowbit(x),res++;//核心  最后一个1 及后面全部为0 ,一直减去直到所有1都减掉,也就是x=0;
        cout<<res<<" ";
    }
    return 0;
}

四、离散化

使用离散化,是因为给的数组长度太长,而我们只需要对很少一部分下标进行操作。

离散化过程:将所需要的所有下标排序,然后分别映射成从1开始的下标(以便运用前缀和)。如图

 对每个给出的x要找到它离散化之后的下标,需要用到二分查找。

存储一个给出的下标和要操作的值,需要用到pair结构体,它有两个成员,first和second。

题目:

 

实现过程:

//这里填你的代码^^
#include<iostream>
#include<vector>
#include<algorithm>
//离散化:将所得的所有下标,排序,,然后按大小顺序从1开始映射。
//用一个vector all存储所得的下标(要排序和去重)。用二分查找找出离散化后的下标(重点)
//其实排序之后,x在all中对应的下标+1就是离散化之后的下标

//用pair  存储输入的下标和对应的值,以及询问的两个端点,注意语法(用中括号和first不是pair中的函数是成员)

using namespace std;

const int N =300010;
typedef pair<int,int> PII;
int n,m;
int a[N],s[N];//a是存储离散化后的数组    s是a的前缀和

vector<int> all;//存储离散化之后的下标
vector<PII> add,query;//add存储插入的数,query存储询问的两个边界
int find(int x)
{
    int l=0,r=all.size()-1;//找到第一个大于等于x的下标  (其实在这题里不会找不到)
    while(l<r)
    {
        int mid=l+r>>1;
        if(all[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r+1;//处理前缀和从下标1开始
}




int main()
{
    cin>>n>>m;
    //输入插入的值
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin>>x>>c;
        add.push_back({x,c});
        all.push_back(x);
    }
    //输入询问
    for(int i=0;i<m;i++)
    {
        int l,r;
        cin>>l>>r;
        all.push_back(l);
        all.push_back(r);
        query.push_back({l,r});
    }
    //排序 去重
    sort(all.begin(),all.end());
    all.erase(unique(all.begin(),all.end()),all.end());
    //往离散化后数组中插入值
    for(auto item :add)
    {
        int x=find(item.first);
        a[x]+=item.second;
    }
    //处理前缀和
    for(int i=1;i<=all.size();i++)   s[i]=s[i-1]+a[i]; 
    //处理询问
    for(auto item : query)
    {
        int l=find(item.first), r= find(item.second);
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;

}
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/3397981/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 五、区间合并

思路:1.用pair存储给出的区间,按左端点排序。然后向右扫描。

2.此时下一个区间与当前区间只有两种关系,包含于不包含。若不包含,则当前区间push进答案中(以后都不会再去合并了),转而维护下一个区间;若包含,则合并(只需更新当前区间右端点)。

3.最后判空,若没有给出过区间则不push,若给出过区间则把当前区间(最后一个维护的区间)push进答案。(利用初始st和ed设为最大数据范围)


//这里填你的代码^^
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

typedef pair<int,int> PII;
vector<PII>  segs;
//1.现按左端点排序,然后遍历所有给出的区间,只存在包含于不包含关系。若包含,则只需要更新又端点。
//若不包含,则现区间push进答案中,然后维护下一段区间。最后判断一下是不是空区间,把最后一个区间push进答案

void  merge(vector<PII> &segs)
{
    vector<PII> res;
    sort(segs.begin(),segs.end());
    int st=-2e9,ed=-2e9;//最大数据范围
    for(auto seg : segs)
    {
        if(seg.first>ed)
        {
            if(st!=-2e9) res.push_back({st,ed});//是第一次则不push,因为留到最后push
            st=seg.first; ed=seg.second;
        }
        else ed=max(ed,seg.second);
    }
    if(st!=-2e9)  res.push_back({st,ed});//如果是空区间就不push
    segs=res;
}


int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int l,r;
        cin>>l>>r;
        segs.push_back({l,r});
    }
    merge(segs);
    cout<<segs.size();
    return 0;
}
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/3398138/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值