难度标识:
⭐:简单,⭐⭐:中等,⭐⭐⭐:困难
。
tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。
1.有效的括号 ⭐
思路
解这题的思路就是先用一个栈去收集遇到的左括号,遇到右括号的时候就将栈里收集的左括号进行弹出匹配,看是否能匹配上。字符串中的左括号按照它们的出现顺序被推入栈,而右括号在出现时则与栈顶的左括号匹配。以下是详细的思路:
-
初始化一个空栈:这个栈用来存放待匹配的左括号。
-
遍历字符串的每个字符:对于字符串中的每一个字符:
-
如果该字符是一个左括号('(','{',或 '['),则将其推入栈中。
-
如果该字符是一个右括号:
- 检查栈是否为空。如果栈为空,那么没有匹配的左括号,因此直接返回
false
。 - 如果栈不为空,弹出栈顶元素,并检查它是否与当前的右括号匹配。如果不匹配,返回
false
。
- 检查栈是否为空。如果栈为空,那么没有匹配的左括号,因此直接返回
-
-
最后,检查栈是否为空 :如果遍历完字符串后栈仍然不为空,这意味着有一些左括号没有被匹配,因此返回
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.最小栈 ⭐
思路
这个问题的关键在于如何在常数时间内检索到最小元素,而不影响栈的基本操作。
核心思路:
-
使用两个栈 :一个用于存放所有元素(我们称其为
mainStack
),另一个用于存放到当前位置为止的最小元素(我们称其为minStack
)。 -
push操作 :每次往
mainStack
中push一个元素时,将这个元素与minStack
的栈顶元素比较:- 如果
minStack
为空或该元素小于minStack
的栈顶元素,则将其也push到minStack
。 - 否则,将
minStack
的栈顶元素再次push到minStack
。这意味着minStack
的栈顶总是当前mainStack
中的最小元素。
- 如果
-
pop操作 :当从
mainStack
中pop一个元素时,也从minStack
中pop一个元素。 -
top操作 :直接返回
mainStack
的栈顶元素。 -
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]]
,我们需要某种方式来保存每个编码级别的状态。使用栈是一个很好的方法,因为它允许我们在进入一个新的编码级别时保存当前的状态,并在完成该级别的解码后回到前一个级别。
步骤如下:
-
初始化两个栈 :一个用于数字(即重复次数),我们称其为
numStk
,另一个用于保存字符串,我们称其为strStk
。 -
遍历输入字符串:对于字符串中的每个字符:
-
如果字符是数字,那么解析所有连续的数字,获取完整的重复次数,并将其压入
numStk
。 -
如果字符是
'['
,则将当前正在构建的字符串压入strStk
并重置当前字符串。 -
如果字符是
']'
,则:- 从
numStk
中pop一个数作为重复次数。 - 重复当前字符串指定的次数。
- 从
strStk
中pop一个字符串,并将其与重复的字符串连接。
- 从
-
如果字符是字母,则添加到当前字符串。
-
-
返回解码后的字符串。
代码
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.每日温度 ⭐
思路
这题的核心思路是使用一个单调递减的栈。该栈用于记录尚未找到更高温度的天的索引。
以下是详细的解决步骤:
-
初始化一个栈:这个栈将存储温度的索引,而不是温度本身。栈的底部元素对应的温度最高,顶部元素对应的温度最低。因此,当我们遇到一个更高的温度时,我们可以从栈的顶部开始,找到所有连续的、更低的温度,并计算它们到这个新的、更高的温度的距离。
-
初始化一个结果数组:用于存储每一天等待下一个更高温度所需的天数。初始时,所有元素都设置为0。
-
遍历温度数组:对于每一个温度:
-
当栈不为空且当前温度高于栈顶索引对应的温度时:
- 从栈中pop出索引。
- 计算两个索引之间的距离,并更新结果数组。
-
将当前索引push入栈。
-
-
返回结果数组。
使用单调递减的栈的好处是,当我们遇到一个更高的温度时,我们可以立即知道哪些天的温度比这个温度低,并且这些天是连续的。因此,我们可以一次性更新这些天的结果。
代码
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.柱状图中最大的矩形 ⭐⭐
思路
这个问题可以使用单调栈的技巧来解决。核心思路是:当一个柱子的高度比栈顶的柱子高度低时,我们知道栈顶的柱子不能向右扩展得到更大的面积。因此,我们可以使用栈来保存柱子的索引,并确保栈中的柱子高度是单调递增的。
以下是详细的解决步骤:
-
初始化一个空的栈:这个栈用来存放柱子的索引。
-
遍历柱子:对于每一个柱子:
-
当栈不为空且当前柱子的高度小于或等于栈顶柱子的高度时,这意味着栈顶柱子的最大面积可以被计算出来。为了计算面积,我们需要知道这个柱子的左边界和右边界。左边界是栈顶的下一个柱子,右边界是当前柱子。
- Pop出栈顶柱子的索引,记为
topIdx
。 - 计算面积:
height = heights[topIdx]
,width = i - stack[stack.length - 1] - 1
,面积 =height * width
。这里,i
是当前柱子的索引。 - 更新最大面积。
- Pop出栈顶柱子的索引,记为
-
将当前柱子的索引推入栈。
-
-
处理栈中剩余的柱子:这一步处理那些一直没有找到比它小的柱子的柱子。对于这些柱子,它们的右边界是数组的末尾,左边界是栈顶的下一个柱子。
-
对于栈中每一个剩余的柱子:
- Pop出栈顶柱子的索引。
- 计算面积:
height = heights[topIdx]
,width = n - stack[stack.length - 1] - 1
,其中n
是柱子的总数。 - 更新最大面积。
-
-
返回最大面积。
代码
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
};
栈的题目相对来说不是很难,如果不理解的话可以看看官方题解,还是很容易明白的。