1 单链表(用数组模拟链表)
- 单链表用的最多的是存储图和树
- e[N]表示当前结点的值,ne[N]表示指向下一个结点。都是整数型数组,空结点的下标用-1表示
- ne[N] = N + 1记录指向下一个结点
int head, e[N], ne[N], idx;
void init()
{
head = -1;
idx = 0;
}
void insert(int a)
{
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
void remove()
{
head = ne[head];
}
1.1 模板
- ne[ ]存储节点的next指针,就是ne[k]里面存的是k+1,就是ne[k] = k + 1
#include<iostream>
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
void init()
{
head = -1;
idx = 0;
}
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++;
}
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx ++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
1.1.1 头部插入元素模板
- 在头部插入元素
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++;
}
1.1.2 第k个位置插入元素模板
- 在第 k 个位置插入元素
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx ++;
}
1.1.3 删除元素模板
void remove(int k)
{
ne[k] = ne[ne[k]];
}
1.2 习题1 —— 826.单链表
Acwing 826.单链表
- 刚开始头结点 head 指向空,第1个点插入点的下标为0,…,第 k 个插入点的下标为 k-1,就是idx的值(idx从0开始)
- H 9:插入第1个点,下标 idx 为 0,值 e 为 9
- I 1 1:第1个点后面插入新的点,即第2个插入的点,下标 idx = 1,值 e 为 1
- D 1:删除第1个插入的点的下一个点,就是下标 idx = 1, 值 e 为 1 的点(这张图有问题,应该删除第2个点),保留下标为0,e = 9的点
- D 0:删除头结点,就是将整个链表删除
- H 6:插入头结点,值 e 为 6,idx = 2;
- I 3 6:第3个插入的点(idx = 2)后面插入点,该点 idx = 3,值为 6
- I 4 5:第4个插入的点(idx = 3)后面插入点,该点 idx = 4,值为 5(第1个插入的点,idx = 0)
- I 4 5:第4个插入的点(idx = 3)后面插入点,该点 idx = 5,值为 5
- I 3 4:第3个插入的点(idx = 2)后面插入点,该点 idx = 6,值为 4
- D 6:删除第6个插入的点的下一个点,就是 idx = 5的点的下一个点,即idx = 4的点(所以这张图也是有问题的,应该删除idx = 4的点)
#include<iostream>
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
void init()
{
head = -1;
idx = 0;
}
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++;
}
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx ++;
}
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];
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;
}
2 双链表
2.1 模板
- 下标0是最左边的点,下标1是最右边的点
- 0是左端点,1是右端点,0号点右边是1号点,1号点的左边是0号点
int e[N], l[N], r[N], idx;
void init()
{
l[1] = 0, r[0] = 1;
idx = 2;
}
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
2.1.1 插入操作
- 必须先修改 l[r[k]] = idx 才能进行 l[k] = idx
void init()
{
l[1] = 0, r[0] = 1;
idx = 2;
}
void add(int k, int x)
{
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
l[k] = idx;
idx ++;
}
2.1.2 删除操作
- 直接将点的左边等于右边,点的右边等于左边
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
2.2 习题1 —— 827.双链表
Acwing 827.双链表
#include<iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
void init()
{
l[1] = 0, r[0] = 1;
idx = 2;
}
void add(int k, int x)
{
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
l[k] = idx;
}
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
3 栈(用数组模拟栈)
3.1 模板
int stk[N], tt = 0;
stk[ ++ tt] = x;
tt -- ;
stk[tt];
if(tt > 0) not empty;
else empty;
#include<iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
stk[++ tt] = x;
tt--;
if(tt > 0) not empty;
else empty;
stk[tt];
3.2 习题1 —— 828.模拟栈
Acwing 828.模拟栈
4 单调栈
- 双指针暴力做法,找到距离i最近的比i小的值,j是从i-1的位置开始找起,即找到i的左边第一个比i小的数(离i最近的小的数)
4.1 模板
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
4.2 习题1 —— 830.单调栈
Acwing 830.单调栈
#include<iostream>
using namespace std;
const int N = 100010;
int stk[N], tt = 0;
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
int x;
cin >> x;
while(tt && stk[tt] >= x) tt--;
if(tt) cout << stk[tt] << ' ';
else cout << -1 << ' ';
stk[++ tt] = x;
}
return 0;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 0; i < n; i++)
{
int x;
scanf("%d",&x);
while(tt && stk[tt] >= x) tt--;
if(tt) printf("%d ",stk[tt]);
else printf("-1");
stk[++ tt] = x;
}
return 0;
}
5 队列
5.1 模板
5.1.1 普通队列
- 队头 hh 在左边,队尾 tt 在右边
- 队头弹出一个数据,则 hh++,队尾插入一个数据,则 tt++
int q[N], hh = 0, tt = -1;
q[ ++ tt] = x;
hh ++ ;
q[hh];
if (hh <= tt)
{
}
int q[N], hh = 0, tt = -1;
q[ ++ tt] = x;
hh ++ ;
q[hh];
q[tt];
if (hh <= tt) not empty
else empty;
5.1.2 循环队列
int q[N], hh = 0, tt = 0;
q[tt ++ ] = x;
if (tt == N) tt = 0;
hh ++ ;
if (hh == N) hh = 0;
q[hh];
if (hh != tt)
{
}
5.2 习题1 —— 829.模拟队列
Acwing 829.模拟队列
6 单调队列
6.1 模板
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ;
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
6.2 习题1 —— 154.滑动窗口
Acwing 154.滑动窗口
- 前面的一个点比后面的一个点大(紧挨着的两个点),在求最小值时,前面大的点没用的,就会被弹出去。就是出现有前面一个点大于后面一个点这样的逆序对,就把大的点删掉。这样数列变成严格单调上升的队列。队头就是最小值
- i是枚举的右端点,k是区间的长度,队列q[hh]存的是下标
- 队头是指滑动窗口左边,队尾是滑动窗口右边。每次移动把新元素插到队尾,队头弹出滑出去的元素
#include<iostream>
using namespace std;
const int N = 1000010;
int n, k;
int q[N], a[N];
int main()
{
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 (hh <= tt && a[q[tt]] >= a[i]) tt --;
q[++ tt] = i;
if(i >= k - 1) printf("%d ",a[q[hh]]);
}
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;
}
7 KMP
- 暴力解法
- next[i] = j:表示数组p在[1,j]这个区间的值与[i-j+1,i]这个区间的值相等,如图1和图2所示。也就是说红色数组,[1, j]和[i-j+1, i]这两段数组相同。比如j=3,i=5,则[1,3]区间的数和[3,5]区间的数相同。
- next[i]里面存放的是[1,i]这个区间最长公共串的长度。
- 图2
- 当蓝色在i位置,红色在 j+1 位置不匹配时,绿色是红色的一部分,使得满足红色数组部分p[1, m] = p[j - m + 1, j]两部分相等。就是红绿部分匹配符合next[j] = m式子。
- 绿色的圈是 i,红色的圈是 j。s[i] 不同于 p[j+1],s[i-1] 相同于 p[j]
- Si 匹配 Pj+1
7.1 模板
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
}
}
7.2 习题1 —— 831.KMP字符串
Acwing 831.KMP字符串
详细解释
- 图中绿色圈和红色的圈不能匹配,就是s[i] != p[j+1]
- 如果不匹配就向后移动一格,ne[j] + 1 = j,在判断ne[j]后面的一个数是否和位置 i的数字相同
#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
for(int i = 2, j = 0; i <= n; i++)
{
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j ++;
ne[i] = j;
}
for(int i = 1, j = 0;i <= m; i++)
{
while(j && s[i] != p[j + 1]) j = ne[j];
if(s[i] == p[j + 1]) j++;
if(j == n)
{
printf("%d ", i - n);
j = ne[j];
}
}
return 0;
}
- j = ne[j]