数据结构(一)
单链表
头插法
 
| 12
 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 ++ ;
 }
 
 | 
一般插入法
 
| 12
 3
 4
 5
 6
 7
 8
 
 | void add(int k, int x)
 {
 e[idx] = x;
 ne[idx] = ne[k];
 ne[k] = idx;
 idx ++ ;
 }
 
 | 
将下标是k的点后面的点删掉
 
| 12
 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$
所有操作保证合法。
输入样例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 10H 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代码
| 12
 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
 
| 12
 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
 
| 12
 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$
所有操作保证合法。
输入样例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 10R 7
 D 1
 L 3
 IL 2 10
 D 3
 IL 2 7
 L 8
 R 9
 IL 4 7
 IR 2 2
 
 | 
输出样例:
| 12
 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$
所有操作保证合法。
输入样例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 10push 5
 query
 push 6
 pop
 query
 pop
 empty
 push 4
 query
 empty
 
 | 
输出样例:
| 12
 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$
所有操作保证合法。
输入样例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 10push 6
 empty
 query
 pop
 empty
 push 3
 push 4
 pop
 query
 push 6
 
 | 
输出样例:

| 12
 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$
输入样例:
输出样例:
| 12
 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$ 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
输出样例:
| 12
 
 | -1 -3 -3 -3 3 33 3 5 5 6 7
 
 | 
| 12
 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
暴力做法
| 12
 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$
| 12
 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;
 }
 
 
 |