数据结构(一)
单链表
头插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
void init() { head = -1; idx = 0; }
void add_to_head(int x) { e[idx] = x; ne[idx] = head; head = idx; idx ++ ; }
|
一般插入法
1 2 3 4 5 6 7 8
| void add(int k, int x) { e[idx] = x; ne[idx] = ne[k]; ne[k] = idx; idx ++ ; }
|
将下标是k的点后面的点删掉
1 2 3 4 5 6
| void remove(int k) { ne[k] = ne[ne[k]]; }
|
把以上的东西结合起来
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 $k$ 个插入的数后面的数;
- 在第$k$ 个插入的数后插入一个数。
现在要对该链表进行 $M$ 次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 $k$ 个插入的数并不是指当前链表的第 $k$ 个数。例如操作过程中一共插入了 $n$ 个数,则按照插入的时间顺序,这 $n$ 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 $n$ 个插入的数。
输入格式
第一行包含整数 $M$,表示操作次数。
接下来 $M$ 行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 $x$。
D k
,表示删除第 k 个插入的数后面的数(当 $k$ 为 0 时,表示删除头结点)。
I k x
,表示在第 k 个插入的数后面插入一个数 $x$(此操作中 $k$ 均大于 0)。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
$1 \leq M \leq 100000$
所有操作保证合法。
输入样例:
1 2 3 4 5 6 7 8 9 10 11
| 10 H 9 I 1 1 D 1 D 0 H 6 I 3 6 I 4 5 I 4 5 I 3 4 D 6
|
输出样例:
AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #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 ++ ; }
void add(int k, int x) { e[idx] = x, ne[idx] = ne[k], ne[k] = 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]; 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; }
|
双链表
在节点a的右边插入一个数x
1 2 3 4 5 6 7 8
| void insert(int a, int x) { e[idx] = x; l[idx] = a, r[idx] = r[a]; l[r[a]] = idx, r[a] = idx; idx ++ ; }
|
删除节点a
1 2 3 4 5 6
| void remove(int a) { l[r[a]] = l[a]; r[l[a]] = r[a]; }
|
实现一个双链表,双链表初始为空,支持 $5$ 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 $k$ 个插入的数删除;
- 在第 $k$ 个插入的数左侧插入一个数;
- 在第 $k$ 个插入的数右侧插入一个数
现在要对该链表进行 $M$ 次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 $k$ 个插入的数并不是指当前链表的第 $k$ 个数。例如操作过程中一共插入了 $n$ 个数,则按照插入的时间顺序,这 $n$ 个数依次为:第 $1$ 个插入的数,第 $2$ 个插入的数,…第 $n$ 个插入的数。
输入格式
第一行包含整数 $M$,表示操作次数。
接下来 $M$ 行,每行包含一个操作命令,操作命令可能为以下几种:
L x
,表示在链表的最左端插入数 $x$。
R x
,表示在链表的最右端插入数 $x$。
D k
,表示将第 $k$ 个插入的数删除。
IL k x
,表示在第 $k$ 个插入的数左侧插入一个数。
IR k x
,表示在第 $k$ 个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。
数据范围
$1 \leq M \leq 100000$
所有操作保证合法。
输入样例:
1 2 3 4 5 6 7 8 9 10 11
| 10 R 7 D 1 L 3 IL 2 10 D 3 IL 2 7 L 8 R 9 IL 4 7 IR 2 2
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include <iostream>
using namespace std;
const int N = 100010;
int m; int e[N], l[N], r[N], idx;
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]; }
int main() { cin >> m;
r[0] = 1, l[1] = 0; idx = 2;
while (m -- ) { string op; cin >> op; int k, x; if (op == "L") { cin >> x; insert(0, x); } 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] << ' '; cout << endl;
return 0; }
|
栈
先进后出
实现一个栈,栈初始为空,支持四种操作:
push x
– 向栈顶插入一个数 xx;
pop
– 从栈顶弹出一个数;
empty
– 判断栈是否为空;
query
– 查询栈顶元素。
现在要对栈进行 MM 个操作,其中的每个操作 $3$ 和操作 $4$ 都要输出相应的结果。
输入格式
第一行包含整数 $M$,表示操作次数。
接下来 $M$ 行,每行包含一个操作命令,操作命令为 push x
,pop
,empty
,query
中的一种。
输出格式
对于每个 empty
和 query
操作都要输出一个查询结果,每个结果占一行。
其中,empty
操作的查询结果为 YES
或 NO
,query
操作的查询结果为一个整数,表示栈顶元素的值。
数据范围
$1 \leq M \leq 100000$
$1 \leq x \leq 10^9$
所有操作保证合法。
输入样例:
1 2 3 4 5 6 7 8 9 10 11
| 10 push 5 query push 6 pop query pop empty push 4 query empty
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream>
using namespace std;
const int N = 100010;
int m; int stk[N], tt;
int main() { cin >> m; while (m -- ) { string op; int x;
cin >> op; if (op == "push") { cin >> x; stk[ ++ tt] = x; } else if (op == "pop") tt -- ; else if (op == "empty") cout << (tt ? "NO" : "YES") << endl; else cout << stk[tt] << endl; }
return 0; }
|
队列
先进先出
实现一个队列,队列初始为空,支持四种操作:
push x
– 向队尾插入一个数 xx;
pop
– 从队头弹出一个数;
empty
– 判断队列是否为空;
query
– 查询队头元素。
现在要对队列进行 MM 个操作,其中的每个操作 33 和操作 44 都要输出相应的结果。
输入格式
第一行包含整数 $M$,表示操作次数。
接下来 $M$ 行,每行包含一个操作命令,操作命令为 push x
,pop
,empty
,query
中的一种。
输出格式
对于每个 empty
和 query
操作都要输出一个查询结果,每个结果占一行。
其中,empty
操作的查询结果为 YES
或 NO
,query
操作的查询结果为一个整数,表示队头元素的值。
数据范围
$1 \leq M \leq 100000$
$1 \leq x \leq 10^9$
所有操作保证合法。
输入样例:
1 2 3 4 5 6 7 8 9 10 11
| 10 push 6 empty query pop empty push 3 push 4 pop query push 6
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream>
using namespace std;
const int N = 100010;
int m; int q[N], hh=0, tt = -1;
int main() { cin >> m;
while (m -- ) { string op; int x;
cin >> op; if (op == "push") { cin >> x; q[ ++ tt] = x; } else if (op == "pop") hh ++ ;从队头弹出 else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl; else cout << q[hh] << endl; }
return 0; }
|
单调栈
给定一个长度为 $N$ 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 $−1$。
输入格式
第一行包含整数 $N$,表示数列长度。
第二行包含 $N$ 个整数,表示整数数列。
输出格式
共一行,包含 $N$ 个整数,其中第 $i$ 个数表示第 $i$ 个数的左边第一个比它小的数,如果不存在则输出 $−1$。
数据范围
$1 \leq N \leq 10^5$
$1 \leq 数列中元素 \leq 10^9$
输入样例:
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main() { int n; cin >> n; while (n -- ) { int x; scanf("%d", &x); while (tt && stk[tt] >= x) tt -- ; if (!tt) printf("-1 "); else printf("%d ", stk[tt]); stk[ ++ tt] = x; }
return 0; }
|
单调队列
给定一个大小为 $n \leq 10^6$的数组。
有一个大小为 $k$ 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 $k$ 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7]
,$k$ 为 $3$。
窗口位置 |
最小值 |
最大值 |
[1 3 -1] -3 5 3 6 7 |
-1 |
3 |
1 [3 -1 -3] 5 3 6 7 |
-3 |
3 |
1 3 [-1 -3 5] 3 6 7 |
-3 |
5 |
1 3 -1 [-3 5 3] 6 7 |
-3 |
5 |
1 3 -1 -3 [5 3 6] 7 |
3 |
6 |
1 3 -1 -3 5 [3 6 7] |
3 |
7 |
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式
输入包含两行。
第一行包含两个整数 $n$ 和 $k$,分别代表数组长度和滑动窗口的长度。
第二行有 $n$ 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
输出样例:
1 2
| -1 -3 -3 -3 3 3 3 3 5 5 6 7
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #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 (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; }
|
kmp
暴力做法
1 2 3 4 5 6 7 8 9 10 11
| s[N], p[M] for (int i = 1; i <= n; i++) { bool flag = true; for (int j = 1; j <= m; j++) { if (s[i + j - 1] != p[j]) { flag = false; break; } } }
|
试题
给定一个字符串 $S$,以及一个模式串 $P$,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串 $P$ 在字符串 $S$ 中多次作为子串出现。
求出模式串 $P$ 在字符串 $S$ 中所有出现的位置的起始下标。
输入格式
第一行输入整数 $N$,表示字符串 $P$ 的长度。
第二行输入字符串 $P$。
第三行输入整数 $M$,表示字符串 $S$ 的长度。
第四行输入字符串 $S$。
输出格式
共一行,输出所有出现位置的起始下标(下标从 $0$ 开始计数),整数之间用空格隔开。
数据范围
$1 \leq N \leq 10^5$
$1 \leq M \leq 10^6$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include <iostream>
using namespace std;
const int N = 100010, M = 1000010;
int n, m; int ne[N]; char s[M], p[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; }
|