394. 字符串解码

class Solution {
public String decodeString(String s) {
Deque<Integer> countStack = new LinkedList<>();
Deque<String> strStack = new LinkedList<>();
StringBuilder res = new StringBuilder();
int multi = 0;
for (char c : s.toCharArray()) {
if (c >= '0' && c <= '9') {
multi = multi * 10 + (c - '0');
} else if (c == '[') {
countStack.push(multi);
strStack.push(res.toString());
multi = 0;
res = new StringBuilder();
} else if (c == ']') {
int repeat = countStack.pop();
String pre = strStack.pop();
// 核心逻辑:先保留原来的结果,然后循环追加 repeat 次
StringBuilder temp = new StringBuilder(pre);
for (int i = 0; i < repeat; i++) {
temp.append(res);
}
res = temp;
} else {
res.append(c);
}
}
return res.toString();
}
}
解题思路1:栈
使用两个栈 保存「之前的状态」,遇到 ] 时触发解码:
-
数字栈(countStack) :保存遇到
[时的重复次数 k。 -
字符串栈(strStack) :保存遇到
[时的当前结果字符串 (即[之前的部分)。 -
变量:
-
currentStr:记录当前正在拼接的字符串。 -
currentNum:记录当前累积的数字(处理多位数)。
-
操作流程:
-
数字 (0-9) :累积到
currentNum(例如123→currentNum = 123)。 -
左括号
[:将当前的currentNum和currentStr分别压入对应栈,然后重置currentNum和currentStr(开始记录括号内的内容)。 -
右括号
]:弹出数字栈顶k和字符串栈顶prevStr。将currentStr重复 k 次,拼接结果为prevStr + currentStr * k,更新给currentStr。 -
字母 :直接拼接到
currentStr末尾。class Solution {
String src; // 保存原始字符串(全局,避免递归传参)
int ptr; // 全局遍历指针,记录当前解析位置public String decodeString(String s) { src = s; ptr = 0; return getString(); // 启动递归解析 } // 核心递归函数:解析当前层级的字符串 public String getString() { // 终止条件:指针越界 或 遇到右括号(当前层级解析完成) if (ptr == src.length() || src.charAt(ptr) == ']') { return ""; } char cur = src.charAt(ptr); int repTime = 1; // 默认重复次数为1(字母场景) String ret = ""; // 当前层级的结果 if (Character.isDigit(cur)) { // 情况1:遇到数字 → 解析 "数字[String]String" 结构 repTime = getDigits(); // 解析连续数字(如123→123) ++ptr; // 跳过左括号 '[' String str = getString(); // 递归解析括号内的字符串(子问题) ++ptr; // 跳过右括号 ']' // 重复拼接:把括号内的字符串重复 repTime 次 while (repTime-- > 0) { ret += str; } } else if (Character.isLetter(cur)) { // 情况2:遇到字母 → 解析 "字母String" 结构 ret = String.valueOf(src.charAt(ptr++)); // 取当前字母,指针后移 } // 关键:拼接当前结果 + 递归解析后续字符串(处理 "XXXYYY" 连续场景) return ret + getString(); } // 辅助函数:解析连续的数字(处理多位数,如 "100[abc]") public int getDigits() { int ret = 0; while (ptr < src.length() && Character.isDigit(src.charAt(ptr))) { // 累积数字:ret = ret*10 + 新数字(字符转数字:c-'0') ret = ret * 10 + src.charAt(ptr++) - '0'; } return ret; }}
解题思路2:递归
这个解法的核心是递归 分解问题,把复杂的嵌套解码拆成更小的子问题:
-
用全局 / 成员变量
src保存原始字符串,ptr作为全局遍历指针(避免递归传参,效率更高); -
递归函数
getString():负责解析「当前层级」的字符串,遇到]或字符串结束时返回; -
辅助函数
getDigits():解析连续的数字(处理多位数)。
递归 语法分析(关键)
官方解法隐含了「上下文无关文法」的思路,把解码规则拆分为 3 种情况:
-
String → 空串(遇到]或字符串结束); -
String → 数字 [ String ] String(嵌套解码); -
String → 字母 String(普通字母拼接)。
739. 每日温度

class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] answer = new int[n];
// 单调栈:存储未找到更高温度的日期索引,保证温度单调递减
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < n; i++) {
// 当前温度 > 栈顶温度 → 栈顶找到了下一个更高温度
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int top = stack.pop();
answer[top] = i - top; // 天数差 = 当前索引 - 栈顶索引
}
// 当前温度 ≤ 栈顶温度 → 入栈等待后续匹配
stack.push(i);
}
return answer;
}
}
解题思路1:单调栈
我们维护一个单调递减栈 ,栈中存储的是未找到更高温度的日期索引 ,保证栈内温度从栈底到栈顶单调递减。
遍历规则(从左到右):
-
对于当前温度
temperatures[i]:-
若当前温度 > 栈顶温度:
-
说明当前温度是栈顶日期的「下一个更高温度」,弹出栈顶索引
top,计算i - top并填入answer[top]。 -
重复此过程,直到栈为空或当前温度 ≤ 栈顶温度。
-
-
若当前温度 ≤ 栈顶温度:将当前索引
i压入栈。
-
-
遍历结束后,栈中剩余的索引代表「之后无更高温度」,
answer中对应位置保持0// 暴力解法(O(n²),n=1e5 时会超时)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] answer = new int[n];
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (temperatures[j] > temperatures[i]) {
answer[i] = j - i;
break;
}
}
}
return answer;
}
}
//这个不超时
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int length = temperatures.length;
int[] ans = new int[length];
int[] next = new int[101];
Arrays.fill(next, Integer.MAX_VALUE);
for (int i = length - 1; i >= 0; --i) {
int warmerIndex = Integer.MAX_VALUE;
for (int t = temperatures[i] + 1; t <= 100; ++t) {
if (next[t] < warmerIndex) {
warmerIndex = next[t];
}
}
if (warmerIndex < Integer.MAX_VALUE) {
ans[i] = warmerIndex - i;
}
next[temperatures[i]] = i;
}
return ans;
}
}
解题思路2:暴力(会超时)
第二个暴力关键点
-
不是纯暴力:该解法的内层循环次数是固定的 100 次(温度范围),而非和数组长度 n 相关,因此时间复杂度是 O (n),不会超时;
-
空间换时间 :用长度为 101 的
next数组记录每个温度最近出现的索引,避免了重复遍历; -
局限性:该解法仅适用于「值域有限且较小」的场景(如本题温度 1~100),若值域扩大到 1~1e5,内层循环 1e5 次会导致 O (n*1e5) = 1e10 次操作,反而超时
84. 柱状图中的最大矩形

class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int maxArea = 0;
// 单调递增栈:存储柱子索引,保证对应高度单调递增
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i <= n; i++) {
// 最后遍历到 i = n,高度视为 0,用于清空栈中剩余元素
int currentHeight = (i == n) ? 0 : heights[i];
// 当前高度 < 栈顶高度 → 弹出栈顶计算面积
while (!stack.isEmpty() && currentHeight < heights[stack.peek()]) {
int top = stack.pop();
int height = heights[top];
// 左边界:新栈顶索引(栈空则为 -1)
int left = stack.isEmpty() ? -1 : stack.peek();
// 右边界:当前索引 i
int width = i - left - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
}
解题思路:单调栈
维护一个单调递增栈 ,栈中存储柱子的索引,保证栈内对应的高度单调递增:
-
遍历每个柱子
i:-
若当前高度
heights[i]< 栈顶高度:-
弹出栈顶索引
top,以heights[top]为高。 -
左边界:新栈顶索引(若栈空则为
-1)。 -
右边界:当前索引
i。 -
宽度 =
i - left - 1,计算面积并更新最大值。(i是边界,面积暂时不参与计算) -
重复此过程,直到栈为空或当前高度 ≥ 栈顶高度。
-
-
若当前高度 ≥ 栈顶高度:将
i压入栈。
-
-
遍历结束后,栈中剩余元素需要继续处理(右边界为数组长度
n)。
示例执行流程(以示例 1: [2,1,5,6,2,3] 为例)
|---|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------|------|---------|
| i | currentHeight | 栈操作 | 面积计算 | maxArea |
| 0 | 2 | 入栈 | - | 0 |
| 1 | 1 | 1<2 → 弹出 0:height=2, left=-1, width=1 → area=2 | 2 | |
| 1 | 1 | 入栈 | - | 2 |
| 2 | 5 | 5>1 → 入栈 | - | 2 |
| 3 | 6 | 6>5 → 入栈 | - | 2 |
| 4 | 2 | 2<6 → 弹出 3:height=6, left=2, width=1 → area=62<5 → 弹出 2:height=5, left=1, width=2 → area=10 | 10 | |
| 4 | 2 | 2>1 → 入栈 | - | 10 |
| 5 | 3 | 3>2 → 入栈 | - | 10 |
| 6 | 0 | 0<3 → 弹出 5:height=3, left=4, width=1 → area=3 0<2 → 弹出 4:height=2, left=1, width=4 → area=8 0<1 → 弹出 1:height=1, left=-1, width=6 → area=6 | 10 | |
最终 maxArea = 10,与示例输出一致
