目录
理论基础
递归调用
栈的自然特点就是后进先出,适合追踪递归调用的返回路径。
在函数调用时,程序会将当前函数的上下文信息存入调用栈,等递归返回时再从栈顶弹出。
典型问题
- 递归求解问题(如阶乘、斐波那契数列)。
- 深度优先搜索(DFS):栈用于追踪回溯路径
表达式求值
栈适合处理表达式的括号匹配、运算符优先级等问题。
通过栈来保存操作数和操作符,逐步求解表达式。
典型问题
- 中缀表达式转后缀表达式。
- 计算后缀表达式。
- 括号匹配:检查括号是否成对匹配。
回溯
涉及回溯时,栈能够帮助保存状态并在必要时进行回退。
典型问题
- 迷宫问题:使用栈保存走过的路径,以便回溯。
- N皇后问题:每次尝试放置一个皇后,无法满足条件时回溯。
支持撤销
在应用程序中,当用户执行某个操作时,可以通过栈来记录之前的操作,从而实现撤销功能。
典型问题
- 文本编辑器中的撤销/重做功能。
- 浏览器的前进/后退按钮。
树的遍历(非递归)
典型问题
- 二叉树的中序、前序、后序遍历:利用栈来记录当前节点,以便在回溯时访问。
单调栈
栈中保留的都是比当前入栈元素大的值,从栈顶到栈底 的元素是单调递增
快速找到一个元素旁边第一个比它大或者比它小的元素
用一个栈来记录遍历过的元素下标i,本质是空间换时间
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < array.length; i++) {
while (!stack.isEmpty() && array[stack.peek()] >= array[i]) {
int index = stack.pop();
// 进行处理
}
stack.push(i);
}
20.有效括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([])"
输出:true
提示:
1 <= s.length <= 104
s 仅由括号 '()[]{}' 组成
class Solution {
public boolean isValid(String s) {
Deque<Character> d=new LinkedList<>();
for(char c:s.toCharArray()){
if(c=='(') d.push(')');
else if(c=='[') d.push(']');
else if(c=='{') d.push('}');
else if(d.isEmpty()||d.peek()!=c) return false;
else d.pop();//注意逻辑,else不能省,自己画图去
}
return d.isEmpty();
}
}
155.最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
-231 <= val <= 231 - 1
pop、top 和 getMin 操作总是在 非空栈 上调用
push, pop, top, and getMin最多被调用 3 * 104 次
辅助栈 minStack用于存放对应主栈不同时期的最小值
- 普通栈
s = [9, 7, 8, 6, 5, 10, 2, 4, 1, 3]
:存储所有元素。 - 辅助栈
ms =[9, 7, 6, 5, 2, 1]
:仅存储当前及历史的最小值。
class MinStack {
Stack<Integer> s,ms;
public MinStack() {//初始化堆栈对象
s=new Stack<>();
ms=new Stack<>();
}
public void push(int val) {//将元素val推入堆栈
s.push(val);
if(ms.isEmpty()||val<=ms.peek()) ms.push(val);//判断元素是否当前最小
}
public void pop() {//删除堆栈顶部的元素
if(s.pop().equals(ms.peek())) ms.pop();//为了确保内容相等,而不是引用相等
}
public int top() {//获取堆栈顶部的元素
return s.peek();
}
public int getMin() {//获取堆栈中的最小元素
return ms.peek();
}
}
方便新加入和删除元素的时候维护最小值
394.字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
提示:
1 <= s.length <= 30
s 由小写英文字母、数字和方括号 '[]' 组成
s 保证是一个 有效 的输入。
s 中所有整数的取值范围为 [1, 300]
'['嵌套开始 ']'嵌套
使用 LinkedList
双端队列模拟栈,addLast()
相当于 push()
,removeLast()
相当于 pop()
class Solution {
public String decodeString(String s) {
LinkedList<Integer> sm = new LinkedList<>();//栈模拟数字,每层嵌套的重复次数
LinkedList<StringBuilder> sr = new LinkedList<>();//栈模拟字符串,每层嵌套时的部分结果
StringBuilder res = new StringBuilder();
int m = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) m = m * 10 + (c - '0'); //数字,处理多位
else if (c == '[') {//左括号,数字和字符入栈,同时重置m和res
sm.addLast(m);
sr.addLast(res);
m = 0;
res = new StringBuilder();
} else if (c == ']') {//右括号,进行解码,存tmp用于重复
StringBuilder tmp = res;
res = sr.removeLast();//弹出以便下一次
res.append(tmp.toString().repeat(sm.removeLast()));
} else res.append(c); //普通字母
}
return res.toString();
}
}
739.每日温度
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100
class Solution {
public int[] dailyTemperatures(int[] t) {
int l = t.length;
int[] r = new int[l];
Deque<Integer> d = new ArrayDeque<>();//存“还没找到更高温度的那些天”的索引
for (int i = 0; i < l; i++) {
while (!d.isEmpty() && t[d.peekLast()] < t[i]) {//当前温度比栈顶温度高
int j = d.pollLast(); //找到了,弹出对应索引
r[j] = i - j;//找到的和现在差了几天
}
d.addLast(i);//比较后加入表示没找到
}
return r;
}
}
84.最大矩形h
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
输入: heights = [2,4]
输出: 4
提示:
1 <= heights.length <=105
0 <= heights[i] <= 104
第 i 位置最大面积:
i 为中心,向左找第一个小于 heights[i] 的位置 left_i;向右找第一个小于于 heights[i] 的位置 right_i,即最大面积为 heights[i] * (right_i - left_i -1)
—>找right_i
和 left_i
class Solution {
public int largestRectangleArea(int[] h) {
ArrayDeque<Integer> d = new ArrayDeque<>();
int a = 0;
for (int i = 0; i <= h.length; i++) {
int curh = (i == h.length) ? 0 : h[i];//末尾假设有一个额外的高度为0的柱子
while (!d.isEmpty() && h[d.peek()] >= curh) {//小于
int height = h[d.pop()];
int width = d.isEmpty() ? i : i - d.peek() - 1; //最左边的柱子宽度为i;其余左边界为i减去栈顶索引再减1
a = Math.max(a, height * width);
}
d.push(i);//存柱子的索引
}
return a;
}
}