文章目录
前言
本文记录力扣Hot100里面关于栈的五道题,包括常见解法和一些关键步骤理解,也有例子便于大家理解
一、有效的括号
1.题目
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([])"
输出:true
示例 5:
输入:s = "([)]"
输出:false
2.代码
步骤:
- 快速校验长度 :首先获取字符串长度,如果长度为奇数,直接判定为无效括号(无法成对闭合),返回
false。 - 定义匹配规则 :创建哈希映射(Map),以右括号为键 、对应左括号为值 ,明确括号的匹配关系(如
)对应(、]对应[等)。 - 初始化栈结构:使用双端队列(Deque)实现栈,用于暂存遍历过程中遇到的左括号。
- 遍历字符串字符 :逐个读取字符串中的每个字符:
- 若当前字符是右括号 (能在Map的键中找到):
- 检查栈是否为空(无左括号可匹配),或栈顶的左括号与当前右括号不匹配,满足任一条件则返回
false; - 若匹配成功,弹出栈顶的左括号(完成一次有效闭合)。
- 检查栈是否为空(无左括号可匹配),或栈顶的左括号与当前右括号不匹配,满足任一条件则返回
- 若当前字符是左括号,直接压入栈中,等待后续右括号匹配。
- 若当前字符是右括号 (能在Map的键中找到):
- 最终校验 :遍历完所有字符后,若栈为空(所有左括号都完成闭合),返回
true;否则返回false。
java
class Solution {
public boolean isValid(String s) {
// 1. 第一步:快速排除无效情况
int n = s.length();
if (n % 2 == 1) {
return false; // 如果字符串长度是奇数,肯定无法成对闭合,直接返回false
}
// 2. 定义括号匹配规则:键是右括号,值是对应的左括号
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
// 3. 初始化栈:用来存储遍历过程中遇到的左括号
// Deque是双端队列,这里当作栈用,LinkedList实现了Deque接口
Deque<Character> stack = new LinkedList<Character>();
// 4. 遍历字符串中的每一个字符
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
// 5. 如果当前字符是右括号(能在pairs的键中找到)
if (pairs.containsKey(ch)) {
// 检查栈是否为空(没有左括号匹配),或栈顶的左括号和当前右括号不匹配
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false; // 匹配失败,直接返回false
}
stack.pop(); // 匹配成功,弹出栈顶的左括号(完成一次闭合)
} else {
// 6. 如果当前字符是左括号,压入栈中等待匹配
stack.push(ch);
}
}
// 7. 遍历结束后,栈必须为空才是有效括号(所有左括号都完成了闭合)
return stack.isEmpty();
}
}
3.例子
示例1:有效括号字符串 s = "()[]{}"
- 长度校验:字符串长度是6(偶数),通过校验。
- 匹配规则 :
pairs映射为{')':'(', ']':'[', '}':'{'}。 - 初始化栈 :栈为空
stack = []。 - 遍历每个字符 :
- 第1个字符
(:是左括号,压入栈 →stack = ['(']。 - 第2个字符
):是右括号,检查栈顶是((与pairs.get(')')匹配),弹出栈顶 →stack = []。 - 第3个字符
[:是左括号,压入栈 →stack = ['[']。 - 第4个字符
]:是右括号,检查栈顶是[(匹配),弹出栈顶 →stack = []。 - 第5个字符
{:是左括号,压入栈 →stack = ['{']。 - 第6个字符
}:是右括号,检查栈顶是{(匹配),弹出栈顶 →stack = []。
- 第1个字符
- 最终校验 :栈为空,返回
true(有效)。
示例2:无效括号字符串 s = "([)]"
- 长度校验:字符串长度是4(偶数),通过校验。
- 匹配规则:同上。
- 初始化栈 :
stack = []。 - 遍历每个字符 :
- 第1个字符
(:左括号,压入栈 →stack = ['(']。 - 第2个字符
[:左括号,压入栈 →stack = ['(', '[']。 - 第3个字符
):右括号,此时pairs.get(')') = '(',但栈顶是[(不匹配),直接返回false(无需继续遍历)。
- 第1个字符
- 最终结果 :返回
false(无效)。
二、最小栈
1.题目
设计一个支持 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
2.代码
步骤:
- 结构设计 :用两个栈,
xStack存所有元素,minStack同步存对应位置的栈内最小值; - 初始化 :双栈置空,
minStack先压入整数最大值作为哨兵; - 入栈(push) :元素入
xStack,同时将"当前minStack栈顶值与该元素的较小值"入minStack; - 出栈(pop):同步弹出两个栈的栈顶元素,保持长度一致;
- 取栈顶(top) :直接返回
xStack栈顶; - 取最小值(getMin) :直接返回
minStack栈顶。
java
class MinStack {
// 1. 定义两个双端队列(当作栈使用)
Deque<Integer> xStack; // 存储所有元素的普通栈
Deque<Integer> minStack; // 存储对应位置最小值的辅助栈
// 2. 构造方法:初始化两个栈
public MinStack() {
xStack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
// 初始化minStack,压入Integer的最大值作为"哨兵"
// 目的是第一次压入元素时,能正确计算最小值(Math.min(MAX_VALUE, x) = x)
minStack.push(Integer.MAX_VALUE);
}
// 3. 压入元素操作
public void push(int x) {
// 3.1 把x压入普通栈
xStack.push(x);
// 3.2 计算当前最小值(辅助栈栈顶的最小值 和 x 取较小值),压入辅助栈
// 核心逻辑:保证minStack的栈顶永远是当前xStack的最小值
minStack.push(Math.min(minStack.peek(), x));
}
// 4. 弹出栈顶元素操作
public void pop() {
// 4.1 弹出普通栈的栈顶
xStack.pop();
// 4.2 同步弹出辅助栈的栈顶(保持两个栈的长度一致)
minStack.pop();
}
// 5. 获取普通栈的栈顶元素
public int top() {
return xStack.peek(); // 查看普通栈栈顶,不移除
}
// 6. 获取当前栈的最小值
public int getMin() {
return minStack.peek(); // 辅助栈的栈顶就是当前最小值
}
}
3.例子
例子:
- 结构 :双栈设计,
xStack存元素,minStack同步存对应最小值; - 初始化 :双栈置空,
minStack先压入Integer.MAX_VALUE(哨兵); - push :元素入
xStack,同时将"minStack栈顶与该元素的较小值"入minStack; - pop:同步弹出双栈栈顶,保持长度一致;
- top :取
xStack栈顶; - getMin :取
minStack栈顶。
执行过程
执行操作序列:push(-2) → push(0) → push(-3) → getMin() → pop() → top() → getMin()
步骤1:初始化 MinStack
xStack = [](空)minStack = [Integer.MAX_VALUE](哨兵值,约2147483647)
步骤2:执行 push(-2)
xStack压入-2 →xStack = [-2]- 计算
Math.min(minStack.peek()(2147483647), -2) = -2,压入minStack→minStack = [2147483647, -2]
步骤3:执行 push(0)
xStack压入0 →xStack = [-2, 0]- 计算
Math.min(minStack.peek()(-2), 0) = -2,压入minStack→minStack = [2147483647, -2, -2]
步骤4:执行 push(-3)
xStack压入-3 →xStack = [-2, 0, -3]- 计算
Math.min(minStack.peek()(-2), -3) = -3,压入minStack→minStack = [2147483647, -2, -2, -3]
步骤5:执行 getMin()
- 取
minStack栈顶 → 返回-3(当前栈内最小值)
步骤6:执行 pop()
- 同步弹出双栈栈顶:
xStack弹出-3 →xStack = [-2, 0]minStack弹出-3 →minStack = [2147483647, -2, -2]
步骤7:执行 top()
- 取
xStack栈顶 → 返回0
步骤8:执行 getMin()
- 取
minStack栈顶 → 返回-2(当前栈内最小值)
三、字符串解码
1.题目
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格 ,且输入的方括号总是符合格式要求 的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
测试用例保证输出的长度不会超过 10(5)。
示例 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"
2.代码
步骤:
- 初始化栈结构:创建一个栈用于存储字符、数字或解码后的字符串片段,核心作用是处理括号的嵌套层级。
- 遍历输入字符串 :逐个处理字符串中的每个字符:
- 若字符不是
]:直接将字符转为字符串压入栈中; - 若字符是
]:触发解码逻辑,分3个子步骤:
① 提取括号内字符串:从栈顶弹出元素,直到遇到[,将弹出的字符拼接成待重复的字符串(拼在前面保证顺序);
② 提取重复次数:弹出[后,继续从栈顶弹出数字字符,拼接成完整数字(处理多位数),转换为整数;
③ 生成重复字符串:将待重复字符串按次数拼接,把结果重新压入栈中;
- 若字符不是
- 拼接最终结果:遍历结束后,栈中剩余的是解码后的片段,依次弹出并拼接到结果字符串前,得到完整的解码结果;
- 返回结果:输出最终拼接好的解码字符串。
java
class Solution {
public String decodeString(String s) {
// 栈:存储字符/解码后的字符串,处理嵌套结构
Stack<String> stack = new Stack<>();
// 遍历字符串每个字符
for (char ch : s.toCharArray()) {
// 非']'直接入栈
if (ch != ']') {
stack.push(String.valueOf(ch));
} else {
// 1. 提取[]内的待重复字符串
String temp = "";
while(!stack.peek().equals(String.valueOf('['))){
temp = stack.pop() + temp; // 拼在前面保证顺序正确
}
stack.pop(); // 弹出左括号[
// 2. 提取重复次数(数字)
String num = "";
while (!stack.isEmpty() && Character.isDigit(stack.peek().charAt(0))) {
num = stack.pop() + num; // 拼在前面处理多位数
}
int repeatNum = num.equals("") ? 0 : Integer.valueOf(num);
// 3. 生成重复后的字符串并入栈
String newStr = "";
for (int i = 0; i < repeatNum; i++) {
newStr += temp;
}
stack.push(newStr);
}
}
// 拼接栈内所有元素得到最终结果
String res = "";
while(!stack.isEmpty()) {
res = stack.pop() + res;
}
return res;
}
}
3.理解
关于Character.isDigit(stack.peek().charAt(0)) 这行代码的解释
1. stack.peek()
stack是代码里定义的Stack<String>类型的栈;peek()是栈的核心方法,作用是获取栈顶的元素,但不弹出 (和pop()不同,pop()会获取并删除);- 因为栈里存的是
String类型,所以stack.peek()的返回值是一个字符串(比如"5"、"["、"a")。
2. stack.peek().charAt(0)
charAt(0)是String类的方法,作用是获取字符串中索引为 0 的字符(也就是第一个字符);- 代码里栈的每个元素都是单个字符转成的字符串(比如把
'5'转成"5",'['转成"["),所以charAt(0)就是拿到这个字符串对应的原始字符; - 示例:
- 如果栈顶是
"5"→charAt(0)得到'5'; - 如果栈顶是
"["→charAt(0)得到'['; - 如果栈顶是
"a"→charAt(0)得到'a'。
- 如果栈顶是
3. Character.isDigit(...)
Character是 Java 中处理字符的工具类;isDigit()是它的静态方法,作用是判断传入的字符是否是 0-9 的数字字符;- 返回值是
boolean类型:是数字返回true,不是返回false; - 示例:
Character.isDigit('5')→true;Character.isDigit('[')→false;Character.isDigit('a')→false。
整行代码的含义
把三部分合起来,Character.isDigit(stack.peek().charAt(0)) 的完整意思是:
查看栈顶的字符串元素,提取它的第一个字符,判断这个字符是否是 0-9 的数字字符。
结合实际代码
这行代码用在提取重复次数的循环条件里:
java
while(!stack.isEmpty() && Character.isDigit(stack.peek().charAt(0))){
num = stack.pop() + num;
}
循环会一直执行的条件是:
- 栈不为空(
!stack.isEmpty()); - 栈顶元素是数字字符(上面解释的这行代码)。
这样就能把连续的数字字符都弹出来拼合成完整的数字(比如栈里是 "1"、"2",会依次弹出拼为 "12"),直到遇到非数字字符(比如 [、字母)为止。
4.例子
例:输入 s = "2[3[a]b]"
先明确栈的初始状态:stack = [](空栈)
步骤1:遍历字符 '2'
ch = '2',不是']'→ 执行stack.push("2")- 栈状态:
["2"]
步骤2:遍历字符 '['
ch = '[',不是']'→ 执行stack.push("[")- 栈状态:
["2", "["]
步骤3:遍历字符 '3'
ch = '3',不是']'→ 执行stack.push("3")- 栈状态:
["2", "[", "3"]
步骤4:遍历字符 '['
ch = '[',不是']'→ 执行stack.push("[")- 栈状态:
["2", "[", "3", "["]
步骤5:遍历字符 'a'
ch = 'a',不是']'→ 执行stack.push("a")- 栈状态:
["2", "[", "3", "[", "a"]
步骤6:遍历字符 ']'(第一个右括号)
这是第一个关键节点,触发解码逻辑:
- 提取方括号内的字符串
temp:- 循环条件:
!stack.peek().equals("[")→ 栈顶是"a",不等于"[" - 执行
temp = stack.pop() + temp→temp = "a" + "" = "a" - 再次检查栈顶:现在栈顶是
"[",循环结束 - 此时
temp = "a"
- 循环条件:
- 弹出
'[':执行stack.pop()→ 栈顶的"["被弹出- 栈状态:
["2", "[", "3"]
- 栈状态:
- 提取重复次数
num:- 循环条件:
!stack.isEmpty() && 栈顶是数字→ 栈顶是"3",是数字 - 执行
num = stack.pop() + num→num = "3" + "" = "3" - 再次检查栈顶:现在栈顶是
"[",不是数字,循环结束 - 转换数字:
repeatNUm = 3
- 循环条件:
- 生成重复后的字符串 :
- 循环 3 次拼接
temp→newStr = "a" + "a" + "a" = "aaa" - 执行
stack.push("aaa") - 栈状态:
["2", "[", "aaa"]
- 循环 3 次拼接
步骤7:遍历字符 'b'
ch = 'b',不是']'→ 执行stack.push("b")- 栈状态:
["2", "[", "aaa", "b"]
步骤8:遍历字符 ']'(第二个右括号,最外层)
触发外层解码逻辑:
- 提取方括号内的字符串
temp:- 循环条件:
!stack.peek().equals("[")→ 栈顶是"b",不等于"["- 第一次循环:
temp = "b" + "" = "b",栈弹出"b"→ 栈:["2", "[", "aaa"] - 第二次循环:栈顶是
"aaa",不等于"["→temp = "aaa" + "b" = "aaab",栈弹出"aaa"→ 栈:["2", "["]
- 第一次循环:
- 栈顶现在是
"[",循环结束 →temp = "aaab"
- 循环条件:
- 弹出
'[':执行stack.pop()→ 栈顶的"["被弹出- 栈状态:
["2"]
- 栈状态:
- 提取重复次数
num:- 循环条件:栈顶是
"2",是数字 → 执行num = "2" + "" = "2" - 转换数字:
repeatNUm = 2
- 循环条件:栈顶是
- 生成重复后的字符串 :
- 循环 2 次拼接
temp→newStr = "aaab" + "aaab" = "aaabaaab" - 执行
stack.push("aaabaaab") - 栈状态:
["aaabaaab"]
- 循环 2 次拼接
步骤9:拼接最终结果
遍历完所有字符后,栈中只剩 "aaabaaab":
- 执行
while(!stack.isEmpty()):- 弹出
"aaabaaab"→res = "aaabaaab" + "" = "aaabaaab"
- 弹出
- 返回
res,最终结果就是"aaabaaab",和预期一致。
补充一个简单例子(无嵌套)
如果输入是 s = "3[a]2[bc]",执行流程更简单,最终结果是 "aaabcbc":
- 解析
3[a]→ 栈中存入"aaa"; - 解析
2[bc]→ 栈中存入"aaa"、"bcbc"; - 拼接栈内元素 →
"aaabcbc"。
四、每日温度
1.题目
给定一个整数数组 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]
2.代码
步骤:
-
前期准备:
- 先获取温度数组的长度,创建一个和数组长度相同的结果数组
ans,数组默认值为0,刚好适配"无更高温度时结果为0"的需求; - 初始化一个单调栈(双端队列实现),栈中存储的是温度数组的下标(而非温度值),目的是后续能直接计算天数差。
- 先获取温度数组的长度,创建一个和数组长度相同的结果数组
-
遍历温度数组 :
从第0天到最后一天依次遍历,对每个下标
i和对应温度temperature:- 进入循环判断:只要栈不为空,且当前温度
temperature大于栈顶下标对应的温度,就说明找到了栈顶下标那一天的"下一个更高温度"; - 计算并存储结果:弹出栈顶下标
prevIndex,用当前下标i减去prevIndex得到天数差,将这个差值存入ans[prevIndex]; - 维持栈的单调性:重复上述循环直到栈为空,或当前温度不大于栈顶下标对应温度,再将当前下标
i压入栈中,等待后续匹配更高温度。
- 进入循环判断:只要栈不为空,且当前温度
-
结果返回 :
遍历完成后,结果数组
ans中已记录每一天到下一个更高温度的天数差,直接返回该数组即可。
java
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
// 1. 获取温度数组长度,初始化结果数组(默认值都是0,符合"无更高温度则为0"的要求)
int length = temperatures.length;
int[] ans = new int[length]; // 初始值全为0,无需额外赋值
// 2. 初始化单调栈,存储温度的下标(而非温度值)
Deque<Integer> stack = new LinkedList<Integer>();
// 3. 遍历每一天的温度
for (int i = 0; i < length; i++) {
int temperature = temperatures[i]; // 当前天的温度
// 4. 核心循环:栈非空 且 当前温度 > 栈顶下标对应的温度
// 说明找到了栈顶下标的"下一个更高温度"
while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
int prevIndex = stack.pop(); // 弹出栈顶下标(找到答案的那天)
ans[prevIndex] = i - prevIndex; // 计算天数差,存入结果数组
}
// 5. 把当前下标压入栈(等待后续找到比它高的温度)
stack.push(i);
}
// 6. 返回结果数组(未找到更高温度的位置保持默认值0)
return ans;
}
}
3.例子
以 temperatures = [73, 74, 75, 71, 69, 72, 76, 73] 为例,
- 温度数组:
[73, 74, 75, 71, 69, 72, 76, 73](下标0到7) - 结果数组
ans初始值:[0, 0, 0, 0, 0, 0, 0, 0] - 单调栈
stack初始为空
-
遍历下标i=0,当前温度73
- 栈状态:空
- 操作:栈为空,直接将下标0压入栈,栈变为
[0] - 结果数组
ans:仍为[0, 0, 0, 0, 0, 0, 0, 0]
-
遍历下标i=1,当前温度74
- 栈状态:
[0](栈顶下标0对应温度73) - 操作:74 > 73,满足"当前温度>栈顶下标对应温度":
- 弹出栈顶下标0,计算
ans[0] = 1 - 0 = 1; - 此时栈为空,将下标1压入栈,栈变为
[1]
- 弹出栈顶下标0,计算
- 结果数组
ans:更新为[1, 0, 0, 0, 0, 0, 0, 0]
- 栈状态:
-
遍历下标i=2,当前温度75
- 栈状态:
[1](栈顶下标1对应温度74) - 操作:75 > 74,满足条件:
- 弹出栈顶下标1,计算
ans[1] = 2 - 1 = 1; - 此时栈为空,将下标2压入栈,栈变为
[2]
- 弹出栈顶下标1,计算
- 结果数组
ans:更新为[1, 1, 0, 0, 0, 0, 0, 0]
- 栈状态:
-
遍历下标i=3,当前温度71
- 栈状态:
[2](栈顶下标2对应温度75) - 操作:71 < 75,不满足条件,直接将下标3压入栈,栈变为
[2, 3] - 结果数组
ans:仍为[1, 1, 0, 0, 0, 0, 0, 0]
- 栈状态:
-
遍历下标i=4,当前温度69
- 栈状态:
[2, 3](栈顶下标3对应温度71) - 操作:69 < 71,不满足条件,直接将下标4压入栈,栈变为
[2, 3, 4] - 结果数组
ans:仍为[1, 1, 0, 0, 0, 0, 0, 0]
- 栈状态:
-
遍历下标i=5,当前温度72
- 栈状态:
[2, 3, 4](栈顶下标4对应温度69) - 操作:
① 72 > 69,弹出下标4,计算ans[4] = 5 - 4 = 1;
② 此时栈顶为3(对应温度71),72 > 71,继续弹出下标3,计算ans[3] = 5 - 3 = 2;
③ 此时栈顶为2(对应温度75),72 < 75,停止循环,将下标5压入栈,栈变为[2, 5] - 结果数组
ans:更新为[1, 1, 0, 2, 1, 0, 0, 0]
- 栈状态:
-
遍历下标i=6,当前温度76
- 栈状态:
[2, 5](栈顶下标5对应温度72) - 操作:
① 76 > 72,弹出下标5,计算ans[5] = 6 - 5 = 1;
② 此时栈顶为2(对应温度75),76 > 75,继续弹出下标2,计算ans[2] = 6 - 2 = 4;
③ 此时栈为空,将下标6压入栈,栈变为[6] - 结果数组
ans:更新为[1, 1, 4, 2, 1, 1, 0, 0]
- 栈状态:
-
遍历下标i=7,当前温度73
- 栈状态:
[6](栈顶下标6对应温度76) - 操作:73 < 76,不满足条件,直接将下标7压入栈,栈变为
[6, 7] - 结果数组
ans:仍为[1, 1, 4, 2, 1, 1, 0, 0]
- 栈状态:
最终结果
遍历结束后,栈中剩余下标6、7(无更高温度匹配),结果数组保持默认值0,最终ans = [1,1,4,2,1,1,0,0]。
五、柱状图中最大的矩形
1.题目
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:

输入: heights = [2,4]
输出: 4
2.代码
步骤:
- 初始化边界数组 :
- 定义
left数组存储每个柱子的左边界(左侧第一个更矮柱子下标),right数组存储右边界(右侧第一个更矮柱子下标); - 将
right数组初始值统一设为数组长度n(作为最右侧的虚拟边界)。
- 定义
- 单调栈找左右边界 :
- 初始化单调栈(存储柱子下标,保证对应高度递增);
- 遍历每个柱子:
① 若栈顶柱子高度≥当前柱子,弹出栈顶并更新其右边界为当前下标;
② 确定当前柱子左边界(栈空则为-1,否则为栈顶下标);
③ 将当前柱子下标压入栈,维持栈的递增特性。
- 计算最大面积 :
- 遍历每个柱子,用公式
(右边界-左边界-1)×柱子高度计算该柱子对应的最大矩形面积; - 不断更新并保留所有面积中的最大值。
- 遍历每个柱子,用公式
- 返回结果:输出最终得到的最大矩形面积。
java
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n]; // 左边界:当前柱子左侧第一个更矮柱子下标
int[] right = new int[n]; // 右边界:当前柱子右侧第一个更矮柱子下标
Arrays.fill(right, n); // 初始化右边界为数组长度(虚拟右边界)
Deque<Integer> mono_stack = new ArrayDeque<Integer>(); // 单调栈存柱子下标
for (int i = 0; i < n; ++i) {
// 弹出更高柱子,更新其右边界为当前i
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
right[mono_stack.peek()] = i;
mono_stack.pop();
}
// 确定当前柱子左边界(栈空则为-1,否则为栈顶)
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
mono_stack.push(i); // 当前下标入栈,维持栈递增
}
int ans = 0;
// 计算每个柱子的最大面积,取最大值
for (int i = 0; i < n; ++i) {
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
}
3.例子
以 heights = [2,1,5,6,2,3]为例:
步骤1:初始化边界数组
n = 6(数组长度);left = [0,0,0,0,0,0](默认值,后续更新);right = [6,6,6,6,6,6](初始化为n=6,虚拟右边界);- 单调栈
mono_stack = [](空栈)。
步骤2:单调栈找左右边界(遍历每个柱子)
遍历 i=0(高度=2)
- 栈为空 → 跳过
while循环; - 栈空 →
left[0] = -1; - 压入下标0 → 栈:
[0];
状态:left = [-1,0,0,0,0,0],right = [6,6,6,6,6,6]。
遍历 i=1(高度=1)
- 栈顶0(高度2 ≥ 1)→ 进入循环:
- 更新
right[0] = 1; - 弹出0 → 栈空,循环结束;
- 更新
- 栈空 →
left[1] = -1; - 压入下标1 → 栈:
[1];
状态:left = [-1,-1,0,0,0,0],right = [1,6,6,6,6,6]。
遍历 i=2(高度=5)
- 栈顶1(高度1 < 5)→ 跳过
while循环; - 栈顶为1 →
left[2] = 1; - 压入下标2 → 栈:
[1,2];
状态:left = [-1,-1,1,0,0,0],right = [1,6,6,6,6,6]。
遍历 i=3(高度=6)
- 栈顶2(高度5 < 6)→ 跳过
while循环; - 栈顶为2 →
left[3] = 2; - 压入下标3 → 栈:
[1,2,3];
状态:left = [-1,-1,1,2,0,0],right = [1,6,6,6,6,6]。
遍历 i=4(高度=2)
- 栈顶3(高度6 ≥ 2)→ 进入循环:
- 更新
right[3] = 4;弹出3 → 栈:[1,2]; - 栈顶2(高度5 ≥ 2)→ 继续循环:
- 更新
right[2] = 4;弹出2 → 栈:[1];
- 更新
- 栈顶1(高度1 < 2)→ 循环结束;
- 更新
- 栈顶为1 →
left[4] = 1; - 压入下标4 → 栈:
[1,4];
状态:left = [-1,-1,1,2,1,0],right = [1,6,4,4,6,6]。
遍历 i=5(高度=3)
- 栈顶4(高度2 < 3)→ 跳过
while循环; - 栈顶为4 →
left[5] = 4; - 压入下标5 → 栈:
[1,4,5];
最终边界数组:
left = [-1,-1,1,2,1,4];right = [1,6,4,4,6,6]。
步骤3:计算最大面积
遍历每个柱子,用公式 (right[i]-left[i]-1) × heights[i] 计算:
| 下标i | 高度 | left[i] | right[i] | 宽度(右-左-1) | 面积 | 当前最大面积 |
|---|---|---|---|---|---|---|
| 0 | 2 | -1 | 1 | 1 | 2 | 2 |
| 1 | 1 | -1 | 6 | 6 | 6 | 6 |
| 2 | 5 | 1 | 4 | 2 | 10 | 10 |
| 3 | 6 | 2 | 4 | 1 | 6 | 10 |
| 4 | 2 | 1 | 6 | 4 | 8 | 10 |
| 5 | 3 | 4 | 6 | 1 | 3 | 10 |
步骤4:返回结果
最终最大面积为 10,与预期一致。
如果本篇文章对您有帮助,可以点赞,收藏或评论哦!!!关注主包不迷路,让我们一起向前进步吧!!