当前位置:   article > 正文

数据结构【上】_链表 给定n个数字,从左到右编号为1~n,接下来进行m次操作,请输出操作后的序列。 操

链表 给定n个数字,从左到右编号为1~n,接下来进行m次操作,请输出操作后的序列。 操

单链表

单链表常用于存储树和图。

实现一个单链表,链表初始为空,支持三种操作
1.向链表头插入一个数;
2.删除第k个插入的数后面的数
3.在第k个插入的数后插入一个数
现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这n个数依次为:第 1个插入的数,第 2个插入的数,..第n 个插入的数
输入格式
第一行包含整数M,表示操作次数
接下来 M行,每行包含一个操作命令,操作命令可能为以下几种
1. H x, 表示向链表插入一个数 t。
2. D k,  表示删除第k个插入的数后面的数(当k为0时,表示删除头结点)。
3. I k x,表示在第 k个插入的数后面插入一个数 x (此操作中均大于0)
输出格式
共一行,将整个链表从头到尾输出
数据范围
1 ≤ M ≤ 100000
所有操作均保证合法。
输入样例:

  1. 10
  2. H 9
  3. I 1 1
  4. D 1
  5. D 0
  6. H 6
  7. I 3 6
  8. I 4 5
  9. I 4 5
  10. I 3 4
  11. D 6

输出样例:

6 4 6 5

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. //elem[] 存值, ne[] 存next地址, h头指针, idx当前
  5. int elem[N], ne[N], h = -1, idx = 1;
  6. //注意:如果idx = 0, 后面的k需要-1,题目中要增删的是第k个数后面的数
  7. //头插
  8. void push_head(int x)
  9. {
  10. elem[idx] = x;//赋值
  11. ne[idx] = h;//指向下一个
  12. h = idx ++;
  13. }
  14. //插入
  15. void push(int k, int x)
  16. {
  17. elem[idx] = x;
  18. ne[idx] = ne[k];
  19. ne[k] = idx ++;
  20. }
  21. //删除
  22. void pop(int k)
  23. {
  24. if(k == 0){
  25. h = ne[h];
  26. }else{
  27. ne[k] = ne[ne[k]];
  28. }
  29. }
  30. int main()
  31. {
  32. int m;
  33. cin >> m;
  34. while(m--){
  35. char op;
  36. int k, x;
  37. cin >> op;
  38. if(op == 'H'){
  39. cin >> x;
  40. push_head(x);
  41. }else if(op == 'I'){
  42. cin >> k >> x;
  43. push(k, x);
  44. }else if(op == 'D'){
  45. cin >> k;
  46. pop(k);
  47. }
  48. }
  49. for(int i = h; i != -1; i = ne[i]){
  50. cout << elem[i] << " ";
  51. }
  52. return 0;
  53. }

双链表

实现-个双链表,双链表初始为空,支持5种操作:
1.在最左侧插入一个数;
2.在最右侧插入一个数;
3.将第k个插入的数删除;
4.在第k个插入的数左侧插入一个数
5.在第k个插入的数右侧插入一个数
现在要对该链表进行M次操作,进行完所有操作后,从左到右输出整个链表
注意:题目中第k个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第 1个插入的数,第2个插入的数,...第 n 个插入的数
输入格式
第一行包含整数M,表示操作次数
接下来 M行,每行包含一个操作命令,操作命令可能为以下几种:
1. L x, 表示在链表的最左端插入数x
2. R x,表示在链表的最右端插入数x
3. D k表示将第k 个插入的数删除。
4. IL k x,表示在第k个插入的数左侧插入一个数
5. IR k x,表示在第k个插入的数右侧插入一个数
输出格式
共一行,将整个链表从左到右输出
数据范围
1≤M≤100000
所有操作保证合法。
输入样例:

  1. 10
  2. R 7
  3. D 1
  4. L 3
  5. IL 2 10
  6. D 3
  7. IL 2 7
  8. L 8
  9. R 9
  10. IL 4 7
  11. IR 2 2

输出样例:

8 7 7 3 2 9

思路:首先初始化,0表示左边,1表示右边。可以看成是两条单链表,一条都是指向左,另一条都是指向右。

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int e[N], l[N], r[N], idx;
  5. void init()
  6. {
  7. l[1] = 0;
  8. r[0] = 1;// 0 表示左边, 1表示右边
  9. idx = 2;
  10. }
  11. //在k后添加 x
  12. void add(int k, int x){
  13. e[idx] = x;//赋值
  14. l[r[k]] = idx;//k的右节点向左指向 idx
  15. l[idx] = k;// idx的向左指向 k
  16. r[idx] = r[k];// idx 向右指向 k的右节点
  17. r[k] = idx ++;//k向右指向 idx
  18. }
  19. // 删除第k个点
  20. void remove(int k)
  21. {
  22. r[l[k]] = r[k];
  23. l[r[k]] = l[k];
  24. }
  25. int main()
  26. {
  27. int m;
  28. cin >> m;
  29. init();
  30. while(m --){
  31. string s;
  32. cin >> s;
  33. int k, x;
  34. if(s == "L"){
  35. cin >> x;
  36. add(0, x);// 最左边是0
  37. }else if(s == "R"){
  38. cin >> x;
  39. add(l[1], x);//最右边是1,1的左节点
  40. }else if(s == "D"){
  41. cin >> k;
  42. remove(k+1);// idx从2开始,所以操作的k要+1
  43. }else if(s == "IL"){
  44. cin >> k >> x;
  45. add(l[k+1], x);
  46. }else if(s == "IR"){
  47. cin >> k >> x;
  48. add(k + 1, x);
  49. }
  50. }
  51. for(int i = r[0]; i != 1; i = r[i]){
  52. cout << e[i] << " ";
  53. }
  54. cout << "\n";
  55. return 0;
  56. }

模拟栈

实现一个栈,栈初始为空,支持四种操作
1. push x  -  向栈插入一个数 x;
2. pop -从栈顶弹出一个数
3. empty - 判断栈是否为空;
4. query - 查询栈顶元素
现在要对栈进行M个操作,其中的每个操作3和操作4都要输出相应的结果。
输入格式
第一行包含整数M,表示操作次数
接下来 M 行,每行包含一个操作命令,操作命令为 push x, pop, empty , query 中的一种
输出格式
对于每个empty 和 query操作都要输出一个查询结果,每个结果占一行。
其中,empty 操作的查询结果为YES或NO. query 操作的查询结果为一个整数,表示栈顶元素的值。
数据范围
1≤M≤100000
1≤x≤10^9
所有操作保证合法
输入样例:

  1. 10
  2. push 5
  3. query
  4. push 6
  5. pop
  6. query
  7. pop
  8. empty
  9. push 4
  10. query
  11. empty

输出样例:

  1. 5
  2. 5
  3. YES
  4. 4
  5. NO

 思路:栈是一个先进后出的数据结构,用数组模拟的时候,可以直接用一个指针idx,添加元素时指针后移,删除元素时指针前移。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int st[N], idx = 0;
  5. int main()
  6. {
  7. int m;
  8. cin >> m;
  9. while(m --){
  10. string s;
  11. cin >> s;
  12. int x;
  13. if(s == "push"){
  14. cin >> x;
  15. st[idx++] = x;
  16. }else if(s == "pop"){
  17. if(idx > 0) idx--;
  18. }else if(s == "query"){
  19. if(idx > 0)
  20. cout << st[idx - 1] << "\n";
  21. }else if(s == "empty"){
  22. if(idx > 0){
  23. puts("NO");
  24. }else{
  25. puts("YES");
  26. }
  27. }
  28. }
  29. return 0;
  30. }

练习题:3302. 表达式求值 - AcWing题库

队列

实现一个队列,队列初始为空,支持四种操作
1. push x  -  向队列插入一个数 x;
2. pop -从队头弹出一个数
3. empty - 判断队列是否为空;
4. query - 查询队头元素
现在要对队列进行M个操作,其中的每个操作3和操作4都要输出相应的结果。
输入格式
第一行包含整数M,表示操作次数
接下来 M 行,每行包含一个操作命令,操作命令为push xquery 中的一种popiempty,
输出格式
对于每个empty和query操作都要输出一个查询结果,每个结果占一行。
其中,empty 操作的查询结果为 YES 或NO,query 操作的查询结果为一个整数,表示队头元素的值
数据范围
1≤M≤100000,
1≤x≤10^9,
所有操作保证合法。
输入样例:

  1. 10
  2. push 6
  3. empty
  4. query
  5. pop
  6. empty
  7. push 3
  8. push 4
  9. pop
  10. query
  11. push 6

输出样例:

  1. NO
  2. 6
  3. YES
  4. 4
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int e[N], h = 0, idx = 0;
  5. int main()
  6. {
  7. int m;
  8. cin >> m;
  9. while(m --){
  10. string s;
  11. cin >> s;
  12. if(s == "push"){
  13. int x; cin >> x;
  14. e[idx++] = x;//插入
  15. }else if(s == "pop"){
  16. if(idx > h){
  17. h ++;//出队,头指针往后移
  18. }
  19. }else if(s == "empty"){
  20. if(h == idx){//判断是否为空
  21. puts("YES");
  22. }else{
  23. puts("NO");
  24. }
  25. }else if(s == "query"){//队头
  26. if(idx > h){
  27. cout << e[h] << "\n";
  28. }
  29. }
  30. }
  31. return 0;
  32. }

单调栈

单调栈维护栈中的元素必须满足单调性【单调上升,单调下降】

给定一个长度为 V的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1。
输入格式
第一行包含整数 N,表示数列长度第二行包含N个整数,表示整数数列
输出格式
共一行,包含N个整数,其中第i个数表示第i个数的左边第一个比它小的数,如果不存在则输出 -1
数据范围

1≤N≤10^5
1≤数列中元素≤10^9
输入样例:

  1. 5
  2. 3 4 2 7 5

输出样例:

-1 3 -1 2 2

暴力:遍历每个数,然后从当前元素往前找,找到第一个比它小的数。

思路:通过栈先进后出的特性,遇到比栈顶元素大的就出栈,知道比它小的就是离当前元素最近且比当前元素小的元素。 

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int n;
  6. cin >> n;
  7. stack<long long > st;
  8. while(n --){
  9. long long x;
  10. cin >> x;
  11. while(!st.empty() && st.top() >= x)st.pop();
  12. if(!st.empty()){
  13. cout << st.top() <<" ";
  14. }else{
  15. cout << "-1 ";
  16. }
  17. st.push(x);
  18. }
  19. return 0;
  20. }

单调队列

【滑动窗口】

给定一个大小为n ≤10 ^ 6的数组
有一个大小为k 的滑动窗口,它从数组的最左边移动到最右边.
你只能在窗口中看到k个数字
每次滑动窗口向右移动一个位置
以下是一个例子:
该数组为[1 3 -1 -3 5 3 6 7],k为3
窗口位置

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式
输入包含两行
第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度第二行有n个整数,代表数组的具体数值
同行数据之间用空格隔开
输出格式
输出包含两个
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值
输入样例:

  1. 8 3
  2. 1 3 -1 -3 5 3 6 7

输出样例:

  1. -1 -3 -3 -3 3 3
  2. 3 3 5 5 6 7

思路:存这个固定窗口 的下标,并且维护窗口的单调性。在窗口滑动过程中,要判断窗口前的元素是否需要出来,以及窗口的个数和单调性。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e6 + 10;
  4. int a[N], q[N];
  5. int main()
  6. {
  7. int n, k; cin >> n >> k;
  8. for(int i = 0; i < n; i++)cin >> a[i];
  9. int h = 0, t = -1;
  10. for(int i = 0; i < n; i++){
  11. if(h <= t && q[h] < i - k + 1)h++;
  12. while(h <= t && a[q[t]] >= a[i])t--;
  13. q[++t] = i;
  14. if(i >= k-1)cout << a[q[h]] << " ";
  15. }
  16. cout << "\n";
  17. h = 0, t = -1;
  18. for(int i = 0; i < n; i++){
  19. if(h <= t && q[h] < i - k + 1)h++;
  20. while(h <= t && a[q[t]] <= a[i])t--;
  21. q[++t] = i;
  22. if(i >= k-1)cout << a[q[h]] << " ";
  23. }
  24. cout << "\n";
  25. return 0;
  26. }

使用deque容器队列实现

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e6 + 10;
  4. int a[N], q[N];
  5. int main()
  6. {
  7. int n, k;
  8. cin >> n >> k;
  9. for(int i = 0; i < n; i++)cin >> a[i];
  10. deque<int> dq;
  11. for(int i = 0; i < n; i++){
  12. if(dq.front() < i - k + 1)dq.pop_front();//队头下标未在窗口内
  13. while(!dq.empty() && a[dq.back()] >= a[i])dq.pop_back();//维持递增序列
  14. dq.push_back(i);
  15. if(i + 1 >= k)cout << a[dq.front()] << " ";//达到长度为k的窗口
  16. }
  17. cout << "\n";
  18. dq.clear();
  19. for(int i = 0; i < n; i++){
  20. if(dq.front() < i - k + 1)dq.pop_front();
  21. while(!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();
  22. dq.push_back(i);
  23. if(i + 1 >= k)cout << a[dq.front()] << " ";
  24. }
  25. return 0;
  26. }

KMP字符串

给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串P在字符串S中多次作为子串出现
求出模式串P在字符串S中所有出现的位置的起始下标
输入格式
第一行输入整数N,表示字符串 P的长度
第二行输入字符串P
第三行输入整数M,表示字符串S的长度
第四行输入字符串S
输出格式
共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例:

  1. 3
  2. aba
  3. 5
  4. ababa

输出样例:

0 2

对于p = "abcab",next数组

pabcab
下标12345
next[]00012

next[1] = 0 , 前缀 ={}, 后缀 = {};
next[2] = 0 , 前缀 ={a}, 后缀 = {b}
next[3] = 0 , 前缀 ={a, ab}, 后缀 = {c, bc}
next[4] = 1 , 前缀 ={a, ab,  abc}, 后缀 = {a, ca, bca}
next[5] = 2 , 前缀 ={a, ab, abc, abca}, 后缀 = {b, ab, cab, bcab}

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 100010, M = 1000010;
  4. // KMP算法
  5. int n, m;
  6. int ne[N];
  7. char s[M], p[N];
  8. int main()
  9. {
  10. cin >> n >> p + 1 >> m >> s + 1;//两个字符串均从下标 1 开始
  11. for(int i = 2, j = 0; i <= n; i++){
  12. while(j && p[i] != p[j+1])j = ne[j];//匹配不上
  13. if(p[i] == p[j+1])j++;
  14. ne[i] = j;
  15. }
  16. for(int i = 1, j = 0; i <= m; i++){
  17. while(j && s[i] != p[j+1])j = ne[j];
  18. if(s[i] == p[j+1])j ++;
  19. if(j == n){
  20. cout << i-n << " ";
  21. j = ne[j];//继续匹配下一个子串
  22. }
  23. }
  24. return 0;
  25. }

本篇讲述最基本的数据结构:单链表,双链表,栈和队列,以及单调栈,单调队列,KMP字符串。对于最基本的数据结构,在C++中可以用stl中的容器 vector, deque,stack和queue。

今天又是努力了的一天!

"困难像弹簧,你弱它就强,你强它更强。" —亨利·福特

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/670196
推荐阅读
相关标签
  

闽ICP备14008679号