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

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

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


🌈个人主页:主页链接


🌈算法专栏:专栏链接


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


🌈LeetCode专栏:专栏链接


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


🌈代码仓库:Gitee链接


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


8036289d7eca4338aa038be5de3d3ee2.jpg


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


双指针:


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

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


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


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


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


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


3afef88c9d924c4eba661a65e67cfc09.gif


之后返回最优解即可:本题就是每当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相关题目推荐:


b3d75345117c4291b42a49003c95b29b.png


至此双指针算法结束.


位运算:


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


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


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


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


087d40e6898142418cc97ef670bd0ff3.jpg


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


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


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


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


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

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


31d44949a34242b19b5dcbb7d4aa8b10.jpg


代码模板:


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


至此位运算结束.


区间合并:


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


d0b012e7ee894413a45f594946d1505d.jpg


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


所以就有以下这三种情况


1ca2cebe2c9a4dd39f19736831536892.jpg


第一种情况是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),若用数组来存储,则会很浪费空间。


举个例子:


bbe869214df34f0eadba9084a12e8432.jpg



图中的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上的一道例题来举例:


f099d72cac1a41c68bf4a867395822fa.png


分析题目可以得到,我们要做的有两件事,第一 在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;
}


至此离散化算法结束.


完结撒花:


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


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


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


🌈诸君,山顶见!

目录
相关文章
|
7月前
|
存储 C语言
函数指针数组:更高效的代码实现方式——指针进阶(二)
函数指针数组:更高效的代码实现方式——指针进阶(二)
29 0
|
7月前
|
小程序 算法
如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙
如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙
32 0
|
6天前
|
C++
【C++】一文深入浅出带你参透库中的几种 [ 智能指针 ]及其背后实现原理(代码&图示)
【C++】一文深入浅出带你参透库中的几种 [ 智能指针 ]及其背后实现原理(代码&图示)
|
7月前
|
算法
代码随想录刷题-数组双指针
代码随想录刷题-数组双指针
32 0
|
9月前
|
算法 C语言 C++
【双指针问题】LeetCode344、345、 844、283问题详解及代码实现
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
49 0
|
6天前
【每日一题Day288】LC344反转字符串 | 双指针 位运算
【每日一题Day288】LC344反转字符串 | 双指针 位运算
12 0
|
10月前
|
程序员 编译器 C语言
C语言指针理解 --- 代码配合图形讲解内存四区
C语言指针理解 --- 代码配合图形讲解内存四区
53 0
|
9月前
|
算法 C语言 C++
【双指针问题】LeetCode:392、3、11问题详解及代码实现
本篇总结过去一周内遇到双指针问题。
79 0
|
10月前
|
算法
双指针算法、位运算
双指针算法、位运算
44 0
|
10月前
力扣82删除排序链表中的重复元素 II:思路分析+代码实现+方法总结(三指针法&快慢指针法【双指针】&递归法)
力扣82删除排序链表中的重复元素 II:思路分析+代码实现+方法总结(三指针法&快慢指针法【双指针】&递归法)
44 0

热门文章

最新文章