数据结构——栈,队列,单调栈|队列,链表,哈希
栈和队列问题:
循环队列问题:
首先要知道队列的最大大小是多少要多开一个空间才能确定到底是空还是满的比如说,如果是 \((rear+1)\)%\(size\)=\(front\), 那么这个队列就是空的,要至少剩余一个空间,就是要相隔两位此时队列已经满了,不能往里面添加元素了!所以有以下操作
保证队列的元素位于1~size之间
push(int x) rear=rear%size+1; pop front%size+1;
query(int x) int t=(front+x-2)%size+1;
查询从对头开始第x个元素!推公式自己找规律
栈递归问题:出栈序列
题目大概内容:现在有一个栈,有 n 个元素,分别为 1,2,…,n。我们可以通过 push 和 pop 操作,将这 n个元素依次放入栈中,然后从栈中弹出,依次把出栈的元素写下来得到的序列就是出栈序列。比如 n=3,如果执行 push 1, push 2, pop, push 3, pop, pop,那么我们 pop 操作得到的元素依次是 2,3,1。也就是说出栈序列就是 2,3,1。请按字典序从小到大的顺序输出所有的 n 个元素的可行的出栈序列。
输入格式
一行一个整数 n
输出格式
按字典序从小到大的顺序一行一行输出所有的 n个元素的可行的出栈序列,每行一个序列,相邻两个数字需要用空格隔开。
分析题目这就是要以递归的形式让我们去构造一个栈去模拟栈不断进队不断出队的过程。很考验自己的创造性,那我们可以构建两个东西,
首先我们想到,肯定是一个个进入栈中如果栈中有元素那么有两种操作,第一个就是弹出栈中的元素,第二个就是把没有进入到栈中的元素放进栈中进行操作,然后容易知道当我们的入栈数组和栈均为空的时候也就是栈已经弹出了n个元素时,就截止。
#include <bits/stdc++.h>
using namespace std;
vector<int>ans;
stack<int> st,ed;//st 是模拟的栈也就是递归中去实现的栈
// 而ed是我们自己要进栈的一个数组
int n;
void solve(int x)
{
if(ans.size()==n)
{ //所有元素都已经出栈进入到答案统计数组中
for(auto t:ans)
{
cout<<t<<' ';
}
cout<<'\n';
return;
}
if(!st.empty())
{//栈非空
int y=st.top();//出栈
st.pop();
ans.push_back(y);
solve(x+1);
//回溯
ans.pop_back();
st.push(y);
}
if(!ed.empty())
{
//有元素没有进入栈中就让他进入栈中!
int y=ed.top();
ed.pop();
st.push(y);
solve(x+1);
ed.push(y);
st.pop();
}
}
int main()
{
cin>>n;
for(int i=n;i>=1;i--)
{
ed.push(i);
//cout<<ed.top();
}
solve(1);
}
链表问题: 翻转序列
这个还没弄清楚
哈希
哈希知识点
字符串哈希
原理:
这里自然溢出哈希很吃香,不然就要双哈希了
题目:
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
ull base=137;//自然溢出哈希无取模操作
ull p[200005];//这个是幂次数组
char s1[200005];
char s2[200005];
ull hs[200005];//这个是哈希字串数组/
int n,m;
int main()
{
cin>>n>>m;
cin>>s1+1>>s2+1;
p[0]=1;
for(int i=1;i<=n;i++)
{
p[i]=p[i-1]*base; //求幂不用快熟幂
}
for(int i=1;i<=n;i++)
{
hs[i]=(hs[i-1]*base+s1[i]-'a');
}
ull calc=0;
for(int i=1;i<=m;i++)
{
calc=(calc*base+s2[i]-'a');//计算哈希值
}
int ans=0;
for(int i=m;i<=n;i++)
{
if((hs[i]-hs[i-m]*p[m])==calc) //实际上*的就说这一段字符串的长度
ans++;
}
cout<<ans;
}
单调栈单调队列
这个很灵活,思维也很巧妙需要多琢磨!
本质是不难的只要按照题目要求去维护一个单调的序列但是实际操作起来却是充满了复杂!
动态区间最大数
维护一个区间窗口保证这个区间是一个单调的就行
#include <bits/stdc++.h>
using namespace std;
pair<int,int> q[4000005];//记录大小和入队的时间
int front=1,rear;
int n,m;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
int x;
for(int i=1;i<=n;i++)
{
cin>>x;
while(front<=rear&&q[rear].first<x)//比这个小的都出队
{
rear--;//这些都是不需要的
}
q[++rear]={x,i};
while(q[front].second<=i-m)//判断队伍头上的要不要出队是否已经过时
++front;
if(i>=m)
cout<<q[front].first<<' ';
}
}
最大矩形面积.
这道题由于当从左往右跑的时候我们可以注意到:我们可以记录一个每个点最左到什么地方,如\(1 2 3 4 2 1\)这一个序列\(0 1 2 3 2 0\)可以记录每一个点最左能到哪儿,如何去维护呢?如果维护一个单调递减的栈,看最左能到哪儿,如果栈中一直都是很大的,说明这些点,都能扩展出去,所以可以出栈直到遇到不能拓展出去的,记录最左边能达到的位置那么这个位置的右边都是能满足要求的了,然后从右往左跑一遍同理!
#include <bits/stdc++.h>
using namespace std;
int a[200005],n;
int l[200005],r[200005];
int s[200005],top;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
while(top&&a[i]<=a[s[top]])
{
top--;
}
if(top)
{
l[i]=s[top];
}else l[i]=0;
s[++top]=i;
}
top=0;
for(int i=n;i;i--)
{
while(top&&a[i]<=a[s[top]])
{
top--;
}
if(top)
{
r[i]=s[top];
}else r[i]=n+1;
s[++top]=i;
}
long long mx=0;
for(int i=1;i<=n;i++)
{
mx=max(mx,(long long)(r[i]-l[i]-1)*a[i]);
}
cout<<mx;
}
这道题目特别有意思:
要求理清逻辑关系:首先对于如果有重复出现的那么再次出现是加上原来的次数,如果前面还有一个就再加上一个,然后更新重复出现的元素个数,如果不是,那么就是一个小于重复出现元素的那么就只能+1,注意有一个出现次数就行!
//常用头文件! //向上取整 ans+mod-1 / mod
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
#define endl '\n'
int t;
void solve()
{
int n;
cin>>n;
vector<ll> a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
ll ans=0;
stack<pair<int,int>> s;
for(int i=1;i<=n;i++)
{
while(!s.empty()&&s.top().first<a[i])
{
ans+=s.top().second;
s.pop();
}
if(!s.empty())
{
if(s.top().first==a[i])
{
ans+=s.top().second;
auto t=s.top();
s.pop();
t.second++;
s.push(t);
if(s.size()>1)
ans++;
}else{
ans++;
s.push({a[i],1});
}
}else{
s.push({a[i],1});
}
}
cout<<ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
//cin>>t;
t=1;
//int i,j,k;
while(t--) solve();
}
这个也是很有意思,想明白了一个地方,还学会了对拍,自己拍数据发现了问题所在就是x移动到右边界时,不会再pop不符合的元素而单次pop可以论证最多也只有一个元素在一次循环中会被pop掉
这道题的思路我们去枚举左端点,记录一个前缀和数组,拿一个单调的指针去跑右边,看右边能跑到哪儿,用一个deque维护,维护一个单调递减的队列,那么队头就是最大的元素,所以每次操作只要取队头就行
//常用头文件! //向上取整 ans+mod-1 / mod
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
#define endl '\n'
#define INF 0x3f3f3f3f //这个是int的最大值 可直接赋值
#define lINF 0x3f3f3f3f3f3f3f //这个是long long 的最大值
int T;
/*
10 1 6
-999 189 -297 -851 -627 -463 -743 16 839 -399 10 0 4
hack while循环内放if的数据
*/
void solve()
{
int n,l,r;
cin>>n>>l>>r;
vector<ll> s(n+1);
deque<pair<ll,ll>> p;
for(int i=1;i<=n;i++)
{
cin>>s[i];
s[i]+=s[i-1];
}
ll x=l;
ll ans=INT64_MIN;
for(int i=1;i<=n-l+1;i++)
{
while(x<=i+r-1&&x<=n)
{
while(!p.empty()&&(p.back().first<= s[x]))
{
p.pop_back();
}
p.push_back({s[x],x++});
}
//一次最多只有一个元素需要pop因为根据i在变化
if(!p.empty()&&p.front().second<i+l-1)//这句话可以论证它的合法性
p.pop_front();
for(auto t:p)
{
cout<<t.first<<' '<<t.second<<' ';
}
cout<<endl;
if(!p.empty())
ans=max(ans,p.front().first-s[i-1]);
}
cout<<ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
//cin>>t;
T=1;
while(T--) solve();
}
这道题就是也是维护两个单调队列维护一个最大值再维护一个最小值,我们去往右边加点只需要考虑一个那就是当前最大值-最小值的范围有无超过h!我们建立两个指针i,j分别为左右端点,表示以i为开头的区间最右能跑到什么地方,如果不能跑了就不断将i的值弹出如i在队列中的话,我们就不断去弹直到可以重新去跑右端点又去跑右端点去了,这个可以保证一个O(n)的一个复杂度,每个点最多只被加入队中一次
//常用头文件! //向上取整 ans+mod-1 / mod
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
ll mn=INT64_MIN;
ll mx=INT64_MAX;
#define endl '\n'
#define INF 0x3f3f3f3f //这个是int的最大值 可直接赋值
#define lINF 0x3f3f3f3f3f3f3f //这个是long long 的最大值
int T;
void solve()
{
int n,h;
cin>>n>>h;
vector<int> a(n+1);
deque<pair<int,int>> s,b;
for(int i=1;i<=n;i++)
cin>>a[i];
int j=0,ans=0;
for(int i=1;i<=n;i++)
{
while(!s.empty()&&s.front().second<i)
s.pop_front();
while(!b.empty()&&b.front().second<i)
b.pop_front();
while(j<=n&&(j<i||s.front().first+h>=b.front().first))
{
j++;
if(j>n) break;
while(s.size()&&s.back().first>=a[j])
s.pop_back();
s.push_back({a[j],j});
while(b.size()&&b.back().first<=a[j])
b.pop_back();
b.push_back({a[j],j});
}
ans=max(ans,j-i);
}
cout<<ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
//cin>>T;
T=1;
while(T--) solve();
}