栈数组模拟+单调窗口
-1是空集
head头节点
e[] 某个点的值 下标是某个点
ne[] 是下个指针
idx存当前已经用到的点
单链表
单链表 idx表示的是第几个插入的数(idx++) 而不是插入的位置
#include <iostream>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;//含义:头节点在下标为1的位置 表示 初始化
idx = 0;//idx下一个新建立的点在下标0处
}
// 将x插到头结点,那么这个x就是头结点,原本的头结点变成第二个结点
void add_to_head(int x)//head存的是链表第一个点的下标
{
e[idx] = x;
ne[idx] = head;
head = idx ++ ;//idx的值是x
}
// 将x插到下标是k的点后面
void add(int k, int x)//k是下标
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx ++ ;//ne是
// idx新点的下一个点在原先k点下一个位置
}
// 将 下标是k的点后面一个点 注意是下标后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m -- )
{
int k, x;
char op;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];//这里常常都漏了
else remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
双链表哦
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;//存值 左和右 idx 少了个head
// 在节点k的右边插入一个数x
void insert(int k, int x)
{
e[idx] = x;//第idx个小标的值设为
l[idx] = k, r[idx] = r[k];//修改idx idx的左边变成k idx的右边变为原来k的右边
l[r[k]] = idx, r[k] = idx ++ ;//修改原数组 右边的左边变为k 而原数的右边变为k
}
// 删除节点k
void remove(int k)
{
l[r[k]] = l[k];//
r[l[k]] = r[k];
}
int main()
{
cin >> m;
// 0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;//idx直接从2开始
while (m -- )
{
string op;
cin >> op;
int k, x;
if (op == "L")
{
cin >> x;
insert(0, x);//0是左端点所以一直插在最左端的右边就可以 只要最后不输出0就好了
}
else if (op == "R")
{
cin >> x;
insert(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1);
}
else if (op == "IL")
{
cin >> k >> x;
insert(l[k + 1], x);
}
else
{
cin >> k >> x;
insert(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';//从0开始 一直遍历到1为止
cout << endl;
return 0;
}
栈
表达式求值https://www.acwing.com/problem/content/3305/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>
using namespace std;
stack<int> num;//num存数组
stack<char> op;//op存操作符
void eval()
{
auto b = num.top(); num.pop();//b==末尾的数字,注意a-b b在后面
auto a = num.top(); num.pop();//a是倒数第二个
auto c = op.top(); op.pop();//取出一个操作符
int x;
if (c == '+') x = a + b;
else if (c == '-') x = a - b;
else if (c == '*') x = a * b;
else x = a / b;
num.push(x);//存进去num
}
int main()
{
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};//哈希表存+-*/快速找到
string str;
cin >> str;//获得表达式
for (int i = 0; i < str.size(); i ++ )//从前面到后面遍历表达式
{
auto c = str[i];//一个个字符
if (isdigit(c))//发现是数字
{
int x = 0, j = i;
while (j < str.size() && isdigit(str[j]))//j从i开始一步一步,使用while如果下一个依然是数字就可以把二位数也存进去
x = x * 10 + str[j ++ ] - '0';
i = j - 1;//如果不是数字了就记得回来,是j-1是因为下面还会i++
num.push(x);
}
else if (c == '(') op.push(c);//左括号放进去
else if (c == ')')//右括号
{
while (op.top() != '(') eval();//一直做运算知道是op栈顶是左括号
op.pop();//删去左括号
}
else//剩下的情况是加减乘除
{
while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();//现在输入的运算符优先级比栈顶的优先级低或相等 先计算栈顶的运算符
//因为(没有计算所以要特判
op.push(c);//再放入操作符
}
}
while (op.size()) eval();//如果还有操作符就从右往左操作
cout << num.top() << endl;
return 0;
}
单调栈https://www.acwing.com/problem/content/832/
题目满足 当ai>=aj &&ai>aj ai永远不会被用到可以输出 所以可以直接输出端点
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int n;
int q[N],tt;
int main()
{ ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
int x;
for (int i = 0; i < n; i ++ ){
cin>>x;
while(tt&&q[tt]>=x) tt--;
if(tt) cout << q[tt]<<" ";//栈顶永远放最小且下标最大的数
else cout << "-1"<<" ";
q[++tt]=x;
}
return 0;
}
单调队列https://www.acwing.com/problem/content/156/
队里存储的是数组下标 判断有没有超出窗口方便 直接输出端点
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N], q[N];
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;//窗口一个元素的下标小了 所以需要移除 移出第一个元素 如果不确定移除多少元素 可以写while
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;//剩余元素中 将队列中的元素比将要加入的元素小的 以后都不会有机会输出了 为了保持hh的元素是最小值 原队列中小的元素减少,
q[ ++ tt] = i;//加入队列,因为上面队列有可能减到0个元素,所以要加入 否则下面输出错误
if (i >= k - 1) printf("%d ", a[q[hh]]);//遍历到的第i个位置 超过k个元素 输出顶部
}
puts("");
hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
return 0;
}
模拟散列表https://www.acwing.com/video/266/
哈希+单链表(head->h[k])
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;//把单链表的head变成h数组
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];//ne[] 和h[] 和 e[] 都是存储idx 不过这次ne 存的是上次h【】的idx 而这次的h[]存的才是这次的idx
h[k] = idx ++ ;
}
bool find(int x)
{
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])//从头开始 往ne遍历
if (e[i] == x)//这里的i是e数组[]的下标
return true;
return false;
}
int main()
{
int n;
scanf("%d", &n);
memset(h, -1, sizeof h);//在单链表里head设置为-1 这里数组就是全-1
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') insert(x);
else
{
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
#include <cstring>
#include <iostream>
using namespace std;
const int N = 200003, null = 0x3f3f3f3f;//这个数大于1e9
int h[N];//只需要开一个数组
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)//位置上有人但是不等于我们要放入的值说明存储了其他值
{
t ++ ;
if (t == N) t = 0;//回到0重新开始找
}
return t;//位置上没人或者等于k
}
int main()
{
memset(h, 0x3f, sizeof h);//开始设为0x3f
int n;
scanf("%d", &n);
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') h[find(x)] = x;
else
{
if (h[find(x)] == null) puts("No");
else puts("Yes");
}
}
return 0;
}
最长单调子序列2https://www.acwing.com/problem/content/898/
法一:
构造一个单调栈 (从0到i 这个操作保证了单调)对每个a[i]在这个栈找第一个小于她的数 把他插在这个数的后面 那么从栈底到a[i]插入位置就是 原数组中能够以a[i]结尾的最长长度
优点 能够输出最长子序列是谁
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int q[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int len = 0;
for (int i = 0; i < n; i ++ )
{
int l = 0, r = len;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(len, r + 1);
q[r + 1] = a[i];
for(int i=0;i<=r+1;i++){
cout << q[i]<<" ";
}cout<<endl;
}
printf("%d\n", len);
return 0;
}
输入
10
1 3 4 2 7 5 6 8 3 3
输出
0 1
0 1 3
0 1 3 4
0 1 2
0 1 2 4 7
0 1 2 4 5
0 1 2 4 5 6
0 1 2 4 5 6 8
0 1 2 3
0 1 2 3
6
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
int n; cin >> n;
vector<int>arr(n);
for (int i = 0; i < n; ++i)cin >> arr[i];
vector<int>stk;//模拟堆栈
stk.push_back(arr[0]);
for (int i = 1; i < n; ++i) {
if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
stk.push_back(arr[i]);
else//替换掉第一个大于或者等于这个数字的那个数
*lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];//通过改变栈里的某个元素 这里不是正常理解的后进后出栈!
}
cout << stk.size() << endl;
return 0;
}
输入
6
3 1 2 1 8 5 6 (最长子序列 1 2 5 6)
输出
栈 1 2 5 6 (虽然不是子序列 但是仍然是这个长度)
使用贪心 希望每一个栈为size长度 为长度的数组 的最后一个数都能最小 长度为i的递增子串中,末尾元素最小的是stk[i] 这样以后我也有更多机会拓展。
比如
1 3 4 2 3 4 7 5 6 8 在看到第3个数的时候 有机会放入第一个3 4 后面还有个2 3 4 所以要把4 换掉 (即1 2 3 4 5 6 8 )
真正的序列
1 3 4 2 7 5 6 8 3 3尽管最后最长序列没有2 但是通过尝试这种可能我们可以在维持最长长度的同时 还能找到更多的可能性让数组变长(1 3 4 5 6 8 )