栈 先入后出的数据结构,可以使用的操作:
入栈: 将元素插入栈顶
出栈:将元素从栈顶弹出
查询:返回栈顶元素 时间复杂度都是$O(1)$
基本思路 用数组模拟栈,不需要考虑空间的问题,这里是算法题
也就是写四个函数,代码很简短也可以直接写在main函数里 push:入栈 pop:出栈 empty:判断是否为空 query:查询栈顶元素
参考代码 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 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 ; }
Reference [1]. yxc [2]. 模拟栈—代码详解 [3]. Bug-Free
基本思路 这道题有一个前置题目,Leetcode150 逆波兰表达式求值(后缀表达式求值) 这题是前缀表达式求值,它相比后缀表达式求值,需要处理括号优先级,所以更麻烦一些
还是用栈模拟中缀表达式的求值过程
开数字栈和操作符栈 若为数字,压栈 若为左括号,压栈 若为右括号,逆向计算直到遇到左括号 若为加减乘除,与操作符栈栈顶比较,如果优先级高,压栈;否则一直计算上一个运算符直到当前运算符优先级高 将剩余的操作符计算完,直到为空
参考代码 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 #include <iostream> #include <cstring> #include <stack> #include <unordered_map> using namespace std; stack<int > num; stack<char > op;void eval () { auto b = num.top (); num.pop (); auto a = num.top (); num.pop (); 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); }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 (isdigit (str[j]) && j < str.size ()) x = x * 10 + str[j++] - '0' ; i = j - 1 ; num.push (x); } else if (c == '(' ) op.push (c); else if (c == ')' ) { while (op.top () != '(' ) eval (); op.pop (); } else { while (op.size () && pr[op.top ()] >= pr[c]) eval (); op.push (c); } } while (op.size ()) eval (); cout<<num.top ()<<endl; return 0 ; }
Reference [1]. yxc [2]. 栈模拟中缀表达式求值过程的简单分析与证明 [3]. 详细注释 [4]. 简单注释 [5]. 详细步骤
单调栈 单调栈就是一种特殊的栈,也就是栈的基础上还具有单调性。 它一般用于解决下类问题 给定一个序列,求序列中的每一个数 左边或右边第一个比他大或比他小的数在什么地方;
如果暴力做法是$O(n^2)$,可以用单调栈优化到$O(n)$
那么如何保证栈内具有单调性,其实也就是每次插入的时候必须要保证单调性 ; 如果可以满足单调性就插入,否则就弹出栈顶,直到可以满足单调性或者栈为空 就插入元素
基本思路 求每一个数左边第一个比它小的数,所以可以用单调递增栈
注意:
单调找相当于是对每个元素进行查询,所以读入序列的方式利用while循环读要方便一点,而不是直接读入数组
这里是stk[tt] >= x 而不是 >,因为我们找的是左边第一个比它小的数,所以需要满足栈内严格单调递增 ,等于也是属于不合法的也需要pop
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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 ; }
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 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int a[N], stk[N], tt;int main () { int n; cin >> n; for (int i = 0 ; i < n; i ++ ) cin >> a[i]; for (int i = 0 ; i < n; i++ ) { while (tt > 0 && stk[tt] >= a[i]) tt--; if (tt > 0 ) cout << stk[tt] << " " ; else cout << -1 << " " ; stk[++tt] = a[i]; } return 0 ; }
Reference [1]. yxc [2]. Hasity-图解,详细注释 [3]. 总结 [4]. 单调栈简单思想
队列 先入先出的数据结构,可以使用的操作:
入队: 将元素插入队尾
出队:将元素从队头弹出
查询:返回队头或者是队尾
时间复杂度都是$O(1)$
基本思路 用数组模拟队列,不需要考虑空间效率
注意边界:
栈我们一般tt = 0
队列的话,一般循环队列,tt初始化成0,队列中的所有元素是q[hh], q[hh + 1], …, q[tt - 1]; 不循环的队列,tt一般初始化成-1,队列中的所有元素是q[hh], q[hh + 1], …, q[tt]
参考代码 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 q[N], hh, 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 ; }
Reference [1]. yxc [2]. 详解,一图胜千言 [3]. Bug-Free [4]. 模拟队列
单调队列 单调队列是一种特殊的队列,它具有单调性,最经典的应用是滑动窗口
基本思路 首先朴素做法,枚举所有的区间(枚举起点 + 长度(常数)),然后每个区间找最大值和最小值,时间复杂度$O(n^2)$会超时,所以就要想办法优化。 这里是利用单调队列来优化
最小值和最大值分开来做,两个for循环完全类似,都做以下四步:
解决队首已经出窗口的问题;
解决队尾与当前元素a[i]不满足单调性的问题;
将当前元素下标加入队尾;
如果满足条件则输出结果;
注意:
这里如果队列存的是下标,队列里维护的元素个数和窗口大小不需要一致,所以while里维护队列的条件是否加等于都可以
如果队列里存的是值,队列里维护的元素个数和窗口大小需要一致,所以while里维护队列的条件不能加 =,这样相等的元素还是会入队列
求最大值之前要重置队列
单调队列我们通常做法,队列里存的是下标(利于判断队头是否出窗口),虽然存的是下标,但是维护我们维护的是值的单调性
单调队列其实维护的是一个双端队列,队头队尾都可以pop,所以用stl来做的话要用deque
参考代码 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 # include <iostream> using namespace std;const int N = 1000010 ;int a[N], q[N], hh, tt = -1 ;int main () { int n, k; cin >> n >> k; for (int i = 0 ; i < n; ++ i) { scanf ("%d" , &a[i]); if (i - k + 1 > q[hh]) ++ hh; while (hh <= tt && a[i] <= a[q[tt]]) -- tt; q[++ tt] = i; if (i + 1 >= k) printf ("%d " , a[q[hh]]); } cout << endl; hh = 0 ; tt = -1 ; for (int i = 0 ; i < n; ++ i) { if (i - k + 1 > q[hh]) ++ hh; while (hh <= tt && a[i] >= a[q[tt]]) -- tt; q[++ tt] = i; if (i + 1 >= k) printf ("%d " , a[q[hh]]); } return 0 ; }
Reference [1]. yxc [2]. 精简步骤 [3]. 详细注释 [4]. 思路启发 [5]. Bug-Free deque写法