【双指针、位运算、离散化、区间合并】思路讲解及代码实现

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。

🌈个人主页:主页链接

🌈算法专栏:专栏链接

        我会一直往里填充内容哒。

🌈LeetCode专栏:专栏链接 

    目前在刷初级算法的LeetBook 。若每日一题当中有力所能及的题目,也会当天做完发出

🌈代码仓库:Gitee链接

🌈点击关注=收获更多优质内容🌈

 

用一篇Blog来讲解下最近学到的双指针、位运算、离散化、区间合并等算法,为日后的刷题打下坚实的基础。

目录

双指针:

代码模板:

Leetcode相关题目推荐:

至此双指针算法结束.

位运算:

 代码模板:

至此位运算结束.

区间合并:

至此区间合并算法结束.

离散化:

先来解决第一个问题:去重

至此离散化算法结束.

完结撒花:


双指针:

双指针,顾名思义就是两个指针。

在解题当中,往往分为两种情况:指向两个不同的序列,指向一个序列。

其核心思想是由暴力算法优化而来,通常暴力算法的时间复杂度为O(n^2),这样解题的过程中显然无法凸显出算法之美(doge

基于此,我们优化了一下解题方式,其核心思想就是有:两个指针i,j当符合某种条件的时候j++,两个指针都不会走已经走过的路

如何理解呢?下面我们来看一个例子

寻找不重复最长区间,就是一个区间里的数字出现次数<=1 

之后返回最优解即可:本题就是每当j移动时就更新下区间

代码模板:

#include<iostream>
using namespace std;
const int N=100010;
int n=0,ans=0;
int a[N],s[N];

void in()
{
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    return;
}
int main()
{
    in();
    for(int i=0,j=0;j<n;j++)
    {
        s[a[j]]++;
        
        while(s[a[j]]>1)
        {
            s[a[i]]--;
            i++;
        }
        
        ans=max(ans,j-i+1);
    }
    cout<<ans;
}

Leetcode相关题目推荐:

至此双指针算法结束.

位运算:

这里先简单介绍一下数据的存储格式,在x86也就是32位的环境下数据是以32个1或0存储在计算机当中的。

抽象来看一个int类型的数据,就可以存储32种情况(有代表1,无代表0)

通常情况下,我们可以利用位运算来存储某几种情况是否出现,若不用位运算,我们就另外需要开辟几个空间来存储这几种情况的bool值。

所以n>>k&1就可以很好的解释上面的算法。

n向右位移k位(这里的k为4),之后与1的二进制位进行&的比较,若有1则输出1.

比较常用的还有一种算法 lowbit(x)

作用是输出一个数二进制位中的最后一个1,通常是用来统计一个数二进制中有几个1等问题。

代码就简单的一行,我们来一起看看他的原理是怎样的。

先来理解一个概念对一个数取相反数 得到的是这个数的补码。(不看符号位)

然后执行&运算,他就会输出最后一位1所代表的十进制。

 代码模板:

int lowbit(x){
    return x&-x;
} 

至此位运算结束.

区间合并:

这个算法比较少用,其大意是将所有有重合的区间进行合并。

 首先我们先将所有端点的头位置(st)进行排序,将尾位置(ed)作为比较依据

所以就有以下这三种情况

第一种情况是ed1<ed的情况 也就是这整个区间被包含在比较区间当中,所以我们做的是保留原区间进行下一个比较。

第二种情况是ed2>ed的情况 也就是比较区间被包含在了其中,所以我们要将比较区间尾端更新,进行下一个比较。

第三种情况是st3>ed 因为我们已经将所有端头进行排序,所以出现这种情况只能说明上一个模板已经排完序了,这时候我们更新比较区间就可以了。

 代码模板:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
vector<PII>segs;
void merge(vector<PII>&segs)
{
    vector<PII>res;
    sort(segs.begin(),segs.end());
    int st=-2e9,ed=-2e9;
    for(auto seg:segs)
    {
        if(ed<seg.first)//第三种情况 
        {
            if(st!=-2e9)res.push_back({st,ed});//因为已经排完序 将上一个区间先放入答案集
            st=seg.first,ed=seg.second;//更新维护区间
        }
        else ed=max(ed,seg.second);//max只能放两个数据类型相同的值
    }
    if(st!=-2e9)res.push_back({st,ed});//放最后一个区间 
    segs=res;
}
int main()
{
    int n=0;
    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();
}

至此区间合并算法结束.

离散化:

离散化先来介绍下是用来干什么的:他是用来解决跨度(10^9)很大,但非常稀疏的数据(10^5),若用数组来存储,则会很浪费空间。

举个例子:

 

图中的key相当于数组下标,而value则是对应的值。我们想要在key为1  10000  5000000的三个地方加上值1,因为数组的空间是连续的,所以这样会造成非常大的空间浪费。

所以为了避免这种浪费,想到了数学中离散化的方法。

首先先来考虑几个问题:

1、key可能会有重复如何去重?(若不去重则会出现相同的下标)

2、如何算出a[i]离散化后的值呢?

先来解决第一个问题:去重

先来认识两个函数,erase与unique。

erase是vector的方法,目的是去除连续的区间

unique是去除指定区间内连续相同数据的函数(从名字也可以来理解 unique唯一的),并返回去除后的尾坐标。

下面我们先对容器进行预处理

sort(alss.begin(),alss.end());
    alss.erase(unique(alss.begin(),alss.end()),alss.end());//去除重复下标

至于第二个问题我们暂且按下不表。

拿acwing上的一道例题来举例:

 分析题目可以得到,我们要做的有两件事,第一 在x的位置上加上常数c,第二访问l,r区间和,看其数据描述,很符合我们上方提到的离散化模型,接下来我们就开始试一试。

先定义变量,其中alss用来存储下标,而add用来将存储的下标与相加的数进行绑定,query用来存储查找的区间。

const int N=300010;
typedef pair<int, int>PII;
int n,m;
int a[N],s[N];//a为离散数组 s为前缀和
vector<int>alss;//原始数组数组
vector<PII>add,query; 

 接下来进行数据读入。

cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin>>x>>c;
        add.push_back({x,c});//将对应坐标与要加的值放在一起之后访问
        alss.push_back(x);//将要访问的下标存入hash
    }

 接下来读入要访问的区间,这里区间也等于是两个坐标,为了之后方便求和,我们也将他放入alss坐标集中

for(int i=0;i<m;i++)
    {
        int l,r;
        cin>>l>>r;
        query.push_back({l,r});//将访问下标存入pair
        alss.push_back(l);//将访问下标也映射到hash中
        alss.push_back(r);
    }

 接下来就是上文提到的去重等问题这里不过多赘述

    sort(alss.begin(),alss.end());
    alss.erase(unique(alss.begin(),alss.end()),alss.end());//去除重复下标

 接下来就是将add中要加的坐标x与要加的数r进行加减。

首先先访问一下要加的坐标是哪个。

这里解答刚刚还剩下的问题,用二分查找去寻找在alss中x放在了哪里,之后返回alss中hash过的坐标,放入a中,最后在a中进行操作。

for(auto item:add)
    {
        int x=find(item.first);//为什么不直接将已经排完序的vector下标各加一作为hash值?
        a[x]+=item.second;//因为这样不知道你要访问的值是哪个 也就是说 在alss中 下标是作为value的存在而你要用的是把这个value做成key key对应的value才是你要操作的空间
    }//总的来说就是节省了时间 也可以一个for去找用的那个值 然后返回其下标但这样无疑慢了很多

二分查找函数

int find(int x)
{
    int l=0,r=alss.size()-1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(alss[mid]>=x)r=mid;
        else l=mid+1;
    }
    return r+1;//整体向前偏移1
}

 最后就是求前缀和,同样要访问的l,r坐标还是通过二分查找去寻找,前缀和前几篇blog已经讲过,不懂的朋友再去看看吧,这里就不再赘述

for(int i=1;i<=alss.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;
    }    

代码模板:

#include<iostream>
#include <utility>
#include<algorithm>
#include<vector>
using namespace std;
const int N=300010;
typedef pair<int, int>PII;
int n,m;
int a[N],s[N];//a为离散数组 s为前缀和
vector<int>alss;//原始数组数组
vector<PII>add,query; 
int find(int x)
{
    int l=0,r=alss.size()-1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(alss[mid]>=x)r=mid;
        else l=mid+1;
    }
    return r+1;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin>>x>>c;
        add.push_back({x,c});//将对应坐标与要加的值放在一起之后访问
        alss.push_back(x);//将要访问的下标存入hash
    }
    for(int i=0;i<m;i++)
    {
        int l,r;
        cin>>l>>r;
        query.push_back({l,r});//将访问下标存入pair
        alss.push_back(l);//将访问下标也映射到hash中
        alss.push_back(r);
    }
    sort(alss.begin(),alss.end());
    alss.erase(unique(alss.begin(),alss.end()),alss.end());//去除重复下标
    for(auto item:add)
    {
        int x=find(item.first);//为什么不直接将已经排完序的vector下标各加一作为hash值?
        a[x]+=item.second;//因为这样不知道你要访问的值是哪个 也就是说 在alss中 下标是作为value的存在而你要用的是把这个value做成key key对应的value才是你要操作的空间
    }//总的来说就是节省了时间 也可以一个for去找用的那个值 然后返回其下标但这样无疑慢了很多
    for(int i=1;i<=alss.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;
}

至此离散化算法结束.

完结撒花:

🌈本篇博客的内容【双指针、位运算、离散化、区间合并思路讲解及代码实现】已经结束。

🌈若对你有些许帮助,可以点赞、关注、评论支持下博主,你的支持将是我前进路上最大的动力。

🌈若以上内容有任何问题,欢迎在评论区指出。若对以上内容有任何不解,都可私信评论询问。

🌈诸君,山顶见!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ppeua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值