正确嵌套的意思是:
- 后出现的左括号必须先匹配(LIFO:后进先出)
- 所以用 栈:遇到左括号就入栈;遇到右括号就用它去匹配栈顶的左括号
规则只有三条:
-
左括号
({[:入栈 -
右括号
)}]:栈必须不空,并且栈顶必须是对应的左括号,否则失败 -
扫描结束:栈必须为空才算成功(不能多出左括号)
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
char[] charsArray = s.toCharArray();
for(char c : charsArray) {
if(c == '(' || c == '{' || c == '[') {
stack.push(c);
} else {
if(stack.isEmpty()) {
return false;
}
char topElem = stack.pop();
if(c == ']' && topElem != '[' || c == '}' && topElem != '{' || c == ')' && topElem != '(') {
return false;
}} } return stack.isEmpty(); }}
核心思路:双栈
stack:正常栈,存所有元素minStack:辅助栈,每一层存"当前主栈中的最小值"
方法具体实现:
push(x):主栈正常压 x;辅助栈压min(x, minStack.peek())pop():两个栈同时 popgetMin():直接返回minStack.peek(),O(1)
关键理解:minStack 的第 i 层,记录的是主栈前 i 个元素中的最小值。主栈 pop 一个,辅助栈也 pop 一个,最小值信息自动回退到上一个状态。
class MinStack {
private Deque<Integer> stack;
private Deque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
}
public void push(int val) {
stack.push(val);
if(minStack.isEmpty()) {
minStack.push(val);
} else {
minStack.push(Math.min(val, minStack.peek()));
}
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
关键是:括号可以嵌套,一旦进入内层,你需要"记住外层的状态",等内层解码完再回到外层继续拼接。
所以用栈保存现场:
- 一个栈存外层的重复次数
k(countStack) - 一个栈存进入括号前已经拼好的字符串
prefix(strStack) - 当前正在构建的字符串用
cur - 当前正在读取的数字用
num(要支持多位数)
处理规则:
-
遇到数字:
num = num*10 + digit -
遇到
[:把num和cur分别入栈,然后清空num=0,cur=""开始处理括号内 -
遇到字母:拼到
cur -
遇到
]:弹出k和prefix,令cur = prefix + cur重复k次class Solution {
public String decodeString(String s) {
Deque<StringBuilder> strStack = new ArrayDeque<>();
Deque<Integer> countStack = new ArrayDeque<>();
int num = 0;
StringBuilder cur = new StringBuilder();
for(int i = 0;i < s.length();++i) {
char c = s.charAt(i);
if(Character.isDigit(c)) {
num = num * 10 + (c - '0');
} else if(c == '[') {
strStack.push(cur);
countStack.push(num);
// 开新的一层
num = 0;
cur = new StringBuilder();
} else if(c == ']') {// 合成操作
int k = countStack.pop();
StringBuilder prefix = strStack.pop();
StringBuilder repeat = new StringBuilder();
for(int j = 0;j < k;++j) {
repeat.append(cur); //这里append的是cur,不是prefix,prefix是用来跟重复的进行合并的
}
cur = prefix.append(repeat);
} else {
cur.append(c);
}
}
return cur.toString();
}
}
这类"找右边第一个更大元素"的典型解法是 单调栈(递减栈):
- 栈里存 下标(不是温度值),方便计算天数差
- 栈保持"从栈底到栈顶,对应温度严格递减"
- 扫描到新一天
i:
-
- 只要
temperatures[i] > temperatures[栈顶],说明栈顶那一天等到了更热的一天,就是今天
- 只要
-
-
- 弹出
idx - 答案
ans[idx] = i - idx
- 弹出
-
-
-
然后把
i入栈(表示它还在等更热的未来)class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] ans = new int[n];
Deque<Integer> stack = new ArrayDeque<>();
for(int i = 0;i < n;++i) {
while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop();
ans[idx] = i - idx;
}
stack.push(i);
}
return ans;
}
}
-
把每根柱子 i 当成"这块矩形的最矮高度",那它能扩到多宽取决于:
- 左边第一个 比它矮 的柱子位置
L - 右边第一个 比它矮 的柱子位置
R
则以 h[i] 为高的最大矩形:
width = R - L - 1area = h[i] * width
所以问题变成:对每个 i,快速找到左右最近更矮元素 → 用单调栈(递增栈)一次扫描解决。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] h = Arrays.copyOf(heights, n + 1);
int ans = 0;
Deque<Integer> stack = new ArrayDeque<>();
for(int i = 0;i <= n;++i) {
while(!stack.isEmpty() && h[i] < h[stack.peek()]) {
int mid = stack.pop();
int left = stack.isEmpty() ? -1 : stack.peek();
int width = i - left - 1;
ans = Math.max(ans, width * h[mid]);
}
stack.push(i);
}
return ans;
}
}