目录
一、双指针
两个指针维护一段区间,可以把暴力算法的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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。