LeetCode热题100栈题解析

难度标识:⭐:简单,⭐⭐:中等,⭐⭐⭐:困难
tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。

1.有效的括号

思路

解这题的思路就是先用一个栈去收集遇到的左括号,遇到右括号的时候就将栈里收集的左括号进行弹出匹配,看是否能匹配上。字符串中的左括号按照它们的出现顺序被推入栈,而右括号在出现时则与栈顶的左括号匹配。以下是详细的思路:

  1. 初始化一个空栈:这个栈用来存放待匹配的左括号。

  2. 遍历字符串的每个字符:对于字符串中的每一个字符:

    • 如果该字符是一个左括号('(','{',或 '['),则将其推入栈中。

    • 如果该字符是一个右括号:

      • 检查栈是否为空。如果栈为空,那么没有匹配的左括号,因此直接返回false
      • 如果栈不为空,弹出栈顶元素,并检查它是否与当前的右括号匹配。如果不匹配,返回false
  3. 最后,检查栈是否为空 :如果遍历完字符串后栈仍然不为空,这意味着有一些左括号没有被匹配,因此返回false。如果栈为空,返回true

这种方法的关键在于,栈保证了我们总是先匹配到最近出现的左括号。这也意味着左括号是以正确的顺序被闭合的。

代码

js 复制代码
var isValid = function (s) {
    const stk = []
    const obj = {
        ']': '[',
        '}': '{',
        ')': '('
    }
    for (let ch of s) {
        if (obj[ch]) {
            const ele = stk.pop()
            if (obj[ch] !== ele) {
                return false
            }
        } else {
            stk.push(ch)
        }
    }
    return stk.length === 0
};

2.最小栈

思路

这个问题的关键在于如何在常数时间内检索到最小元素,而不影响栈的基本操作。

核心思路:

  1. 使用两个栈 :一个用于存放所有元素(我们称其为mainStack),另一个用于存放到当前位置为止的最小元素(我们称其为minStack)。

  2. push操作 :每次往mainStack中push一个元素时,将这个元素与minStack的栈顶元素比较:

    • 如果minStack为空或该元素小于minStack的栈顶元素,则将其也push到minStack
    • 否则,将minStack的栈顶元素再次push到minStack。这意味着minStack的栈顶总是当前mainStack中的最小元素。
  3. pop操作 :当从mainStack中pop一个元素时,也从minStack中pop一个元素。

  4. top操作 :直接返回mainStack的栈顶元素。

  5. getMin操作 :返回minStack的栈顶元素。因为我们已经确保了minStack的栈顶总是当前mainStack中的最小元素,所以这个操作是O(1)时间复杂度的。

代码

js 复制代码
var MinStack = function () {
    this.mainStk = []
    this.minStk = []
};
MinStack.prototype.push = function (val) {
    this.mainStk.push(val)
    if (this.minStk.length === 0 || val < this.getMin()) {
        this.minStk.push(val)
    } else {
        this.minStk.push(this.getMin())
    }
};
MinStack.prototype.pop = function () {
    this.mainStk.pop()
    this.minStk.pop()
};
MinStack.prototype.top = function () {
    return this.mainStk[this.mainStk.length - 1]
};
MinStack.prototype.getMin = function () {
    return this.minStk[this.minStk.length - 1]
};

3.字符串解码

思路

这题的核心思路是使用栈来帮助进行解码。由于字符串中可能包含嵌套的编码,例如3[a2[c]],我们需要某种方式来保存每个编码级别的状态。使用栈是一个很好的方法,因为它允许我们在进入一个新的编码级别时保存当前的状态,并在完成该级别的解码后回到前一个级别。

步骤如下:

  1. 初始化两个栈 :一个用于数字(即重复次数),我们称其为numStk,另一个用于保存字符串,我们称其为strStk

  2. 遍历输入字符串:对于字符串中的每个字符:

    • 如果字符是数字,那么解析所有连续的数字,获取完整的重复次数,并将其压入numStk

    • 如果字符是'[',则将当前正在构建的字符串压入strStk并重置当前字符串。

    • 如果字符是']',则:

      • numStk中pop一个数作为重复次数。
      • 重复当前字符串指定的次数。
      • strStk中pop一个字符串,并将其与重复的字符串连接。
    • 如果字符是字母,则添加到当前字符串。

  3. 返回解码后的字符串

代码

js 复制代码
var decodeString = function (s) {
    const numStk = [], strStk = []
    let currNum = 0, currStr = ''
    for (let ch of s) {
        if (ch >= '0' && ch <= '9') {
            currNum = currNum * 10 + parseInt(ch)
        } else if (ch === '[') {
            numStk.push(currNum)
            strStk.push(currStr)
            currNum = 0
            currStr = ''
        } else if (ch === ']') {
            let prevStr = strStk.pop()
            let times = numStk.pop()
            currStr = prevStr + currStr.repeat(times)
        } else {
            currStr += ch
        }
    }
    return currStr
};

4.每日温度

思路

这题的核心思路是使用一个单调递减的栈。该栈用于记录尚未找到更高温度的天的索引。

以下是详细的解决步骤:

  1. 初始化一个栈:这个栈将存储温度的索引,而不是温度本身。栈的底部元素对应的温度最高,顶部元素对应的温度最低。因此,当我们遇到一个更高的温度时,我们可以从栈的顶部开始,找到所有连续的、更低的温度,并计算它们到这个新的、更高的温度的距离。

  2. 初始化一个结果数组:用于存储每一天等待下一个更高温度所需的天数。初始时,所有元素都设置为0。

  3. 遍历温度数组:对于每一个温度:

    • 当栈不为空且当前温度高于栈顶索引对应的温度时:

      • 从栈中pop出索引。
      • 计算两个索引之间的距离,并更新结果数组。
    • 将当前索引push入栈。

  4. 返回结果数组

使用单调递减的栈的好处是,当我们遇到一个更高的温度时,我们可以立即知道哪些天的温度比这个温度低,并且这些天是连续的。因此,我们可以一次性更新这些天的结果。

代码

js 复制代码
var dailyTemperatures = function (temperatures) {
    const n = temperatures.length, stk = []
    const answer = new Array(n).fill(0)
    for (let i = 0; i < n; i++) {
        while (stk.length && temperatures[i] > temperatures[stk[stk.length - 1]]) {
            const lastDay = stk.pop()
            answer[lastDay] = i - lastDay
        }
        stk.push(i)
    }
    return answer
};

5.柱状图中最大的矩形 ⭐⭐

思路

这个问题可以使用单调栈的技巧来解决。核心思路是:当一个柱子的高度比栈顶的柱子高度低时,我们知道栈顶的柱子不能向右扩展得到更大的面积。因此,我们可以使用栈来保存柱子的索引,并确保栈中的柱子高度是单调递增的。

以下是详细的解决步骤:

  1. 初始化一个空的栈:这个栈用来存放柱子的索引。

  2. 遍历柱子:对于每一个柱子:

    • 当栈不为空且当前柱子的高度小于或等于栈顶柱子的高度时,这意味着栈顶柱子的最大面积可以被计算出来。为了计算面积,我们需要知道这个柱子的左边界和右边界。左边界是栈顶的下一个柱子,右边界是当前柱子。

      • Pop出栈顶柱子的索引,记为 topIdx
      • 计算面积:height = heights[topIdx]width = i - stack[stack.length - 1] - 1,面积 = height * width。这里,i 是当前柱子的索引。
      • 更新最大面积。
    • 将当前柱子的索引推入栈。

  3. 处理栈中剩余的柱子:这一步处理那些一直没有找到比它小的柱子的柱子。对于这些柱子,它们的右边界是数组的末尾,左边界是栈顶的下一个柱子。

    • 对于栈中每一个剩余的柱子:

      • Pop出栈顶柱子的索引。
      • 计算面积:height = heights[topIdx]width = n - stack[stack.length - 1] - 1,其中 n 是柱子的总数。
      • 更新最大面积。
  4. 返回最大面积

代码

js 复制代码
var largestRectangleArea = function (heights) {
    const stk = []
    let maxArea = 0
    for (let i = 0; i <= heights.length; i++) {
        let currHeight = i === heights.length ? 0 : heights[i]
        while (stk.length && currHeight < heights[stk[stk.length - 1]]) {
            const height = heights[stk.pop()]
            const width = stk.length === 0 ? i : i - stk[stk.length - 1] - 1
            maxArea = Math.max(maxArea, width * height)
        }
        stk.push(i)
    }
    return maxArea
};

栈的题目相对来说不是很难,如果不理解的话可以看看官方题解,还是很容易明白的。

相关推荐
Ning_.16 分钟前
力扣第 66 题 “加一”
算法·leetcode
kitesxian16 分钟前
Leetcode437. 路径总和 III(HOT100)
算法·深度优先
YSRM19 分钟前
异或-java-leetcode
java·算法·leetcode
木向22 分钟前
leetcode:222完全二叉树的节点个数
算法·leetcode
Ning_.29 分钟前
力扣第 67 题 “二进制求和”
算法·leetcode
IT 青年36 分钟前
数据结构 (11)串的基本概念
数据结构
悦涵仙子1 小时前
vueuse中的useTemplateRefsList
前端·javascript·vue.js
bbppooi1 小时前
堆的实现(完全注释版本)
c语言·数据结构·算法·排序算法
FFDUST1 小时前
C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)
c语言·c++·算法·leetcode
m0_738054561 小时前
【leetcode】全排列 回溯法
c++·算法·leetcode·回溯法