结构
先进后出;
分类
- 顺序栈(用数组实现ArrayDeque);
- 链式栈(由链表实现LinkedDeque)。
时间复杂度O(1)
- 因为无论是顺序栈还是链式栈,入栈、出栈都只涉及栈顶个别数据的操作;
- 例如顺序栈实际上就是操作数组的最后一个元素,链式栈实际上就是对头节点做增删。
扩容
出栈、入栈的时间复杂度都为O(1);
入栈的时候有两种情况:
- 一种是可能会涉及到数据迁移时间复杂度为O(n);
- 一种是会普通的入栈操作时间复杂度为O(1);
- 通过将耗时多的入栈操作的时间均摊到其他入栈操作上,平均时间复杂度为O(1)。
应用
函数调用栈
- 操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量;
- 每次进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
表达式求值
- 通过两个栈实现,一个栈保存操作数、一个栈保存运算符。
- 从左到右遍历表达式,当遇到数字,直接压入操作数栈;
- 当遇到运算符,就与运算符栈顶元素进行比较。如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶元素运算符,从操作数栈的栈顶取2个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
例如:
中缀表达式转换为后缀表达式
常见算法
- 772. 基本计算器 III
- 227. 基本计算器 II
- 224. 基本计算器
class Solution { public int calculate(String s) { Deque<Integer> deque = new ArrayDeque<>(); Deque<Character> ops = new ArrayDeque<>(); int i = 0; while(i < s.length()){ char c = s.charAt(i); if(c == ' '){ i++; continue; }else if(isDigit(c)){ // 将数组压入栈中 int number = 0; while(i < s.length() && isDigit(s.charAt(i))){ number = number * 10 + (s.charAt(i) - '0'); i++; } deque.push(number); continue; }else if(c == '('){ ops.push(c); }else if(c == ')'){ // 括号的部分进行运算 while(ops.peek() != '('){ int temp = cal(deque.pop(), deque.pop(), ops.pop()); deque.push(temp); } ops.pop(); }else{ // 操作符的优先级进行排序、大于则压入,小于则将压入计算之后再次比较 if(ops.isEmpty() || !findFirst(ops.peek(), c)){ ops.push(c); }else{ int temp = cal(deque.pop(), deque.pop(), ops.pop()); deque.push(temp); continue; } } i++; } while(!ops.isEmpty()){ int temp = cal(deque.pop(), deque.pop(), ops.pop()); deque.push(temp); } return deque.pop(); } public int cal(int nums1, int nums2, char c){ if(c == '+'){ return nums1 + nums2; }else if(c == '-'){ return nums2 - nums1; }else if(c == '*'){ return nums2 * nums1; }else{ return nums2 / nums1; } } public boolean findFirst(char top, char c){ return top != '(' && (top == '*' || top == '/' || c == '+' || c == '-'); } public boolean isDigit(char c){ return (c >= '0' && c <= '9'); } }
有效括号
class Solution { public boolean isValid(String s) { Map<Character, Character> map = new HashMap<>(); map.put(')', '('); map.put(']', '['); map.put('}', '{'); Deque<Character> deque = new ArrayDeque<>(); for(int i = 0; i < s.length(); i++){ char temp = s.charAt(i); if(!deque.isEmpty() && deque.peek() == map.get(temp)){ deque.pop(); }else{ deque.push(temp); } } return deque.isEmpty(); } }最小栈
class MinStack { Deque<Integer> min; Deque<Integer> deque; public MinStack() { min = new ArrayDeque<>(); deque = new ArrayDeque<>(); } public void push(int val) { deque.push(val); if(min.isEmpty() || min.peek() >= val){ min.push(val); } } public void pop() { if(deque.pop().equals(min.peek())){ min.pop(); } } public int top() { return deque.peek(); } public int getMin() { return min.peek(); } }两个栈实现队列
class CQueue { Deque<Integer> deque; Deque<Integer> help; public CQueue() { deque = new ArrayDeque<>(); help = new ArrayDeque<>(); } public void appendTail(int value) { help.push(value); } public int deleteHead() { if(deque.isEmpty()){ while(!help.isEmpty()){ deque.push(help.pop()); } } return deque.isEmpty() ? -1 : deque.pop(); } }150. 逆波兰表达式求值
class Solution { public int evalRPN(String[] tokens) { Deque<Integer> deque = new ArrayDeque<>(); int nums1 = 0, nums2 = 0; for(String i : tokens){ switch(i){ case("+"): nums1 = deque.pop(); nums2 = deque.pop(); deque.push(nums2 + nums1); break; case("-"): nums1 = deque.pop(); nums2 = deque.pop(); deque.push(nums2 - nums1); break; case("*"): nums1 = deque.pop(); nums2 = deque.pop(); deque.push(nums2 * nums1); break; case("/"): nums1 = deque.pop(); nums2 = deque.pop(); deque.push(nums2 / nums1); break; default: deque.push(Integer.parseInt(i)); } } return deque.pop(); } }单调栈:解决当前位置的下一个更大元素问题
递增栈的固定模板:
- 496. 下一个更大元素 I
- 503. 下一个更大元素 II
- 剑指 Offer II 038. 每日温度
- 739. 每日温度
- 42. 接雨水
int[] res = new int[n]; //存储下标 Deque<Integer> deque = new ArrayDeque<>(); for(int i = 0; i < n; i++){ //如果当前元素大于栈里的元素、说明当前元素就是栈首的下一个元素 while(!deque.isEmpty() && arr[i] > arr[deque.peek()]){ res[deque.pop()] = arr[i]; } //继续下一个元素 deque.push(i); }递减栈的固定模板:
- 84. 柱状图中最大的矩形
- 962. 最大宽度坡
-
1124. 表现良好的最长时间段
class Solution { public int largestRectangleArea(int[] heights) { // 核心思路:找到左右两侧第一个小于当前值的元素 // deque是单调递减栈,栈中的下一个元素就是左侧小于当前值的元素,右侧是i // 前面填0可以不需要判断栈为空,后面填0为了让栈中的元素全部拿出 int[] arr = new int[heights.length + 2]; System.arraycopy(heights, 0, arr, 1, heights.length); int res = 0, n = arr.length; Deque<Integer> deque = new ArrayDeque<>(); for(int i = 0; i < n; i++){ while(!deque.isEmpty() && arr[deque.peek()] > arr[i]){ int h = arr[deque.pop()]; res = Math.max(res, h * (i - deque.peek() - 1)); } deque.push(i); } return res; } }