结构
先进后出;
分类
- 顺序栈(用数组实现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;
}
}

京公网安备 11010502036488号