力扣Hot100系列15(Java)——[二叉树]总结(有效的括号,最小栈,字符串解码,每日温度,柱状图中最大的矩形)

文章目录


前言

本文记录力扣Hot100里面关于栈的五道题,包括常见解法和一些关键步骤理解,也有例子便于大家理解


一、有效的括号

1.题目

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"

输出:true

示例 2:

输入:s = "()[]{}"

输出:true

示例 3:

输入:s = "(]"

输出:false

示例 4:

输入:s = "([])"

输出:true

示例 5:

输入:s = "([)]"

输出:false

2.代码

步骤

  1. 快速校验长度 :首先获取字符串长度,如果长度为奇数,直接判定为无效括号(无法成对闭合),返回false
  2. 定义匹配规则 :创建哈希映射(Map),以右括号为键对应左括号为值 ,明确括号的匹配关系(如)对应(]对应[等)。
  3. 初始化栈结构:使用双端队列(Deque)实现栈,用于暂存遍历过程中遇到的左括号。
  4. 遍历字符串字符 :逐个读取字符串中的每个字符:
    • 若当前字符是右括号 (能在Map的键中找到):
      • 检查栈是否为空(无左括号可匹配),或栈顶的左括号与当前右括号不匹配,满足任一条件则返回false
      • 若匹配成功,弹出栈顶的左括号(完成一次有效闭合)。
    • 若当前字符是左括号,直接压入栈中,等待后续右括号匹配。
  5. 最终校验 :遍历完所有字符后,若栈为空(所有左括号都完成闭合),返回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 = "()[]{}"

  1. 长度校验:字符串长度是6(偶数),通过校验。
  2. 匹配规则pairs 映射为 {')':'(', ']':'[', '}':'{'}
  3. 初始化栈 :栈为空 stack = []
  4. 遍历每个字符
    • 第1个字符 (:是左括号,压入栈 → stack = ['(']
    • 第2个字符 ):是右括号,检查栈顶是 ((与 pairs.get(')') 匹配),弹出栈顶 → stack = []
    • 第3个字符 [:是左括号,压入栈 → stack = ['[']
    • 第4个字符 ]:是右括号,检查栈顶是 [(匹配),弹出栈顶 → stack = []
    • 第5个字符 {:是左括号,压入栈 → stack = ['{']
    • 第6个字符 }:是右括号,检查栈顶是 {(匹配),弹出栈顶 → stack = []
  5. 最终校验 :栈为空,返回 true(有效)。

示例2:无效括号字符串 s = "([)]"

  1. 长度校验:字符串长度是4(偶数),通过校验。
  2. 匹配规则:同上。
  3. 初始化栈stack = []
  4. 遍历每个字符
    • 第1个字符 (:左括号,压入栈 → stack = ['(']
    • 第2个字符 [:左括号,压入栈 → stack = ['(', '[']
    • 第3个字符 ):右括号,此时 pairs.get(')') = '(',但栈顶是 [(不匹配),直接返回 false(无需继续遍历)。
  5. 最终结果 :返回 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.代码

步骤:

  1. 结构设计 :用两个栈,xStack存所有元素,minStack同步存对应位置的栈内最小值;
  2. 初始化 :双栈置空,minStack先压入整数最大值作为哨兵;
  3. 入栈(push) :元素入xStack,同时将"当前minStack栈顶值与该元素的较小值"入minStack
  4. 出栈(pop):同步弹出两个栈的栈顶元素,保持长度一致;
  5. 取栈顶(top) :直接返回xStack栈顶;
  6. 取最小值(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.例子

例子:

  1. 结构 :双栈设计,xStack存元素,minStack同步存对应最小值;
  2. 初始化 :双栈置空,minStack先压入Integer.MAX_VALUE(哨兵);
  3. push :元素入xStack,同时将"minStack栈顶与该元素的较小值"入minStack
  4. pop:同步弹出双栈栈顶,保持长度一致;
  5. top :取xStack栈顶;
  6. 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,压入minStackminStack = [2147483647, -2]

步骤3:执行 push(0)

  • xStack压入0 → xStack = [-2, 0]
  • 计算Math.min(minStack.peek()(-2), 0) = -2,压入minStackminStack = [2147483647, -2, -2]

步骤4:执行 push(-3)

  • xStack压入-3 → xStack = [-2, 0, -3]
  • 计算Math.min(minStack.peek()(-2), -3) = -3,压入minStackminStack = [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.代码

步骤:

  1. 初始化栈结构:创建一个栈用于存储字符、数字或解码后的字符串片段,核心作用是处理括号的嵌套层级。
  2. 遍历输入字符串 :逐个处理字符串中的每个字符:
    • 若字符不是 ]:直接将字符转为字符串压入栈中;
    • 若字符是 ]:触发解码逻辑,分3个子步骤:
      ① 提取括号内字符串:从栈顶弹出元素,直到遇到 [,将弹出的字符拼接成待重复的字符串(拼在前面保证顺序);
      ② 提取重复次数:弹出 [ 后,继续从栈顶弹出数字字符,拼接成完整数字(处理多位数),转换为整数;
      ③ 生成重复字符串:将待重复字符串按次数拼接,把结果重新压入栈中;
  3. 拼接最终结果:遍历结束后,栈中剩余的是解码后的片段,依次弹出并拼接到结果字符串前,得到完整的解码结果;
  4. 返回结果:输出最终拼接好的解码字符串。
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;
}

循环会一直执行的条件是:

  1. 栈不为空(!stack.isEmpty());
  2. 栈顶元素是数字字符(上面解释的这行代码)。

这样就能把连续的数字字符都弹出来拼合成完整的数字(比如栈里是 "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:遍历字符 ']'(第一个右括号)

这是第一个关键节点,触发解码逻辑:

  1. 提取方括号内的字符串 temp
    • 循环条件:!stack.peek().equals("[") → 栈顶是 "a",不等于 "["
    • 执行 temp = stack.pop() + temptemp = "a" + "" = "a"
    • 再次检查栈顶:现在栈顶是 "[",循环结束
    • 此时 temp = "a"
  2. 弹出 '[' :执行 stack.pop() → 栈顶的 "[" 被弹出
    • 栈状态:["2", "[", "3"]
  3. 提取重复次数 num
    • 循环条件:!stack.isEmpty() && 栈顶是数字 → 栈顶是 "3",是数字
    • 执行 num = stack.pop() + numnum = "3" + "" = "3"
    • 再次检查栈顶:现在栈顶是 "[",不是数字,循环结束
    • 转换数字:repeatNUm = 3
  4. 生成重复后的字符串
    • 循环 3 次拼接 tempnewStr = "a" + "a" + "a" = "aaa"
    • 执行 stack.push("aaa")
    • 栈状态:["2", "[", "aaa"]

步骤7:遍历字符 'b'

  • ch = 'b',不是 ']' → 执行 stack.push("b")
  • 栈状态:["2", "[", "aaa", "b"]

步骤8:遍历字符 ']'(第二个右括号,最外层)

触发外层解码逻辑:

  1. 提取方括号内的字符串 temp
    • 循环条件:!stack.peek().equals("[") → 栈顶是 "b",不等于 "["
      • 第一次循环:temp = "b" + "" = "b",栈弹出 "b" → 栈:["2", "[", "aaa"]
      • 第二次循环:栈顶是 "aaa",不等于 "["temp = "aaa" + "b" = "aaab",栈弹出 "aaa" → 栈:["2", "["]
    • 栈顶现在是 "[",循环结束 → temp = "aaab"
  2. 弹出 '[' :执行 stack.pop() → 栈顶的 "[" 被弹出
    • 栈状态:["2"]
  3. 提取重复次数 num
    • 循环条件:栈顶是 "2",是数字 → 执行 num = "2" + "" = "2"
    • 转换数字:repeatNUm = 2
  4. 生成重复后的字符串
    • 循环 2 次拼接 tempnewStr = "aaab" + "aaab" = "aaabaaab"
    • 执行 stack.push("aaabaaab")
    • 栈状态:["aaabaaab"]

步骤9:拼接最终结果

遍历完所有字符后,栈中只剩 "aaabaaab"

  • 执行 while(!stack.isEmpty())
    • 弹出 "aaabaaab"res = "aaabaaab" + "" = "aaabaaab"
  • 返回 res,最终结果就是 "aaabaaab",和预期一致。

补充一个简单例子(无嵌套)

如果输入是 s = "3[a]2[bc]",执行流程更简单,最终结果是 "aaabcbc"

  1. 解析 3[a] → 栈中存入 "aaa"
  2. 解析 2[bc] → 栈中存入 "aaa""bcbc"
  3. 拼接栈内元素 → "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.代码

步骤:

  1. 前期准备

    • 先获取温度数组的长度,创建一个和数组长度相同的结果数组ans,数组默认值为0,刚好适配"无更高温度时结果为0"的需求;
    • 初始化一个单调栈(双端队列实现),栈中存储的是温度数组的下标(而非温度值),目的是后续能直接计算天数差。
  2. 遍历温度数组

    从第0天到最后一天依次遍历,对每个下标i和对应温度temperature

    • 进入循环判断:只要栈不为空,且当前温度temperature大于栈顶下标对应的温度,就说明找到了栈顶下标那一天的"下一个更高温度";
    • 计算并存储结果:弹出栈顶下标prevIndex,用当前下标i减去prevIndex得到天数差,将这个差值存入ans[prevIndex]
    • 维持栈的单调性:重复上述循环直到栈为空,或当前温度不大于栈顶下标对应温度,再将当前下标i压入栈中,等待后续匹配更高温度。
  3. 结果返回

    遍历完成后,结果数组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初始为空
  1. 遍历下标i=0,当前温度73

    • 栈状态:空
    • 操作:栈为空,直接将下标0压入栈,栈变为[0]
    • 结果数组ans:仍为[0, 0, 0, 0, 0, 0, 0, 0]
  2. 遍历下标i=1,当前温度74

    • 栈状态:[0](栈顶下标0对应温度73)
    • 操作:74 > 73,满足"当前温度>栈顶下标对应温度":
      • 弹出栈顶下标0,计算ans[0] = 1 - 0 = 1
      • 此时栈为空,将下标1压入栈,栈变为[1]
    • 结果数组ans:更新为[1, 0, 0, 0, 0, 0, 0, 0]
  3. 遍历下标i=2,当前温度75

    • 栈状态:[1](栈顶下标1对应温度74)
    • 操作:75 > 74,满足条件:
      • 弹出栈顶下标1,计算ans[1] = 2 - 1 = 1
      • 此时栈为空,将下标2压入栈,栈变为[2]
    • 结果数组ans:更新为[1, 1, 0, 0, 0, 0, 0, 0]
  4. 遍历下标i=3,当前温度71

    • 栈状态:[2](栈顶下标2对应温度75)
    • 操作:71 < 75,不满足条件,直接将下标3压入栈,栈变为[2, 3]
    • 结果数组ans:仍为[1, 1, 0, 0, 0, 0, 0, 0]
  5. 遍历下标i=4,当前温度69

    • 栈状态:[2, 3](栈顶下标3对应温度71)
    • 操作:69 < 71,不满足条件,直接将下标4压入栈,栈变为[2, 3, 4]
    • 结果数组ans:仍为[1, 1, 0, 0, 0, 0, 0, 0]
  6. 遍历下标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]
  7. 遍历下标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]
  8. 遍历下标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.代码

步骤:

  1. 初始化边界数组
    • 定义left数组存储每个柱子的左边界(左侧第一个更矮柱子下标),right数组存储右边界(右侧第一个更矮柱子下标);
    • right数组初始值统一设为数组长度n(作为最右侧的虚拟边界)。
  2. 单调栈找左右边界
    • 初始化单调栈(存储柱子下标,保证对应高度递增);
    • 遍历每个柱子:
      ① 若栈顶柱子高度≥当前柱子,弹出栈顶并更新其右边界为当前下标;
      ② 确定当前柱子左边界(栈空则为-1,否则为栈顶下标);
      ③ 将当前柱子下标压入栈,维持栈的递增特性。
  3. 计算最大面积
    • 遍历每个柱子,用公式(右边界-左边界-1)×柱子高度计算该柱子对应的最大矩形面积;
    • 不断更新并保留所有面积中的最大值。
  4. 返回结果:输出最终得到的最大矩形面积。
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)

  1. 栈为空 → 跳过 while 循环;
  2. 栈空 → left[0] = -1
  3. 压入下标0 → 栈:[0]
    状态:left = [-1,0,0,0,0,0]right = [6,6,6,6,6,6]

遍历 i=1(高度=1)

  1. 栈顶0(高度2 ≥ 1)→ 进入循环:
    • 更新 right[0] = 1
    • 弹出0 → 栈空,循环结束;
  2. 栈空 → left[1] = -1
  3. 压入下标1 → 栈:[1]
    状态:left = [-1,-1,0,0,0,0]right = [1,6,6,6,6,6]

遍历 i=2(高度=5)

  1. 栈顶1(高度1 < 5)→ 跳过 while 循环;
  2. 栈顶为1 → left[2] = 1
  3. 压入下标2 → 栈:[1,2]
    状态:left = [-1,-1,1,0,0,0]right = [1,6,6,6,6,6]

遍历 i=3(高度=6)

  1. 栈顶2(高度5 < 6)→ 跳过 while 循环;
  2. 栈顶为2 → left[3] = 2
  3. 压入下标3 → 栈:[1,2,3]
    状态:left = [-1,-1,1,2,0,0]right = [1,6,6,6,6,6]

遍历 i=4(高度=2)

  1. 栈顶3(高度6 ≥ 2)→ 进入循环:
    • 更新 right[3] = 4;弹出3 → 栈:[1,2]
    • 栈顶2(高度5 ≥ 2)→ 继续循环:
      • 更新 right[2] = 4;弹出2 → 栈:[1]
    • 栈顶1(高度1 < 2)→ 循环结束;
  2. 栈顶为1 → left[4] = 1
  3. 压入下标4 → 栈:[1,4]
    状态:left = [-1,-1,1,2,1,0]right = [1,6,4,4,6,6]

遍历 i=5(高度=3)

  1. 栈顶4(高度2 < 3)→ 跳过 while 循环;
  2. 栈顶为4 → left[5] = 4
  3. 压入下标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,与预期一致。


如果本篇文章对您有帮助,可以点赞,收藏或评论哦!!!关注主包不迷路,让我们一起向前进步吧!!

相关推荐
ahauedu2 小时前
SpringBoot 3.5.10引入springdoc-openapi-starter-webmvc-ui版本
java·spring boot·后端
C蔡博士2 小时前
算法设计与分析:稳定配对(Stable Matching)问题
算法·算法设计·复杂度分析
拾光Ծ2 小时前
【优选算法】双指针算法:专题二
c++·算法·双指针·双指针算法·c++算法·笔试面试
沉默-_-2 小时前
MyBatis 学习笔记
java·开发语言·tomcat
未来龙皇小蓝2 小时前
Spring内置常见线程池配置及相关概念
java·后端·spring·系统架构
Elias不吃糖2 小时前
Java 常用数据结构:API + 实现类型 + 核心原理 + 例子 + 选型与性能(完整版)
java·数据结构·性能·实现类
会游泳的石头2 小时前
构建企业级知识库智能问答系统:基于 Java 与 Spring Boot 的轻量实现
java·开发语言·spring boot·ai
YuTaoShao2 小时前
【LeetCode 每日一题】3650. 边反转的最小路径总成本
算法·leetcode·职场和发展
j_xxx404_2 小时前
C++算法入门:滑动窗口合集(长度最小的子数组|无重复字符的最长字串|)
开发语言·c++·算法