一、柱状图中最大的矩形
此题为leetcode第84题
思路:枚举每一根柱子i的高度hight[i],随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于h。换句话说,我们需要找到左右两侧最近的高度小于h的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于h,并且就是i能够扩展到的最远范围。使用单调栈可以实现这样的功能,即栈中元素都是单调递增或递减的。这里我们使用单调递增的栈。
- (1)当前位第i个柱子,栈中存放j值,j为i左边的柱子。从栈底到栈顶,j的值严格单调递增,同时对应的高度值也严格单调递增。(2)当我们枚举到第i根柱子时,我们从栈顶不断地移除height[j] > height[i]的j值。在移除完毕后,栈顶的j值就一定满足height[j] < height[i],此时j就是i左侧且最近的小于其高度的柱子。这里会有一种特殊情况。如果我们移除了栈中所有的j值,那就说明i左侧所有柱子的高度都大于height[i],那么我们可以认为i左侧且最近的小于其高度的柱子在位置 j=−1,它是一根「虚拟」的、高度无限低的柱子。这样的定义不会对我们的答案产生任何的影响,我们也称这根「虚拟」的柱子为「哨兵」。(3)我们再将i放入栈顶。
- 根据以上步骤,我们从左往右遍历得到每个柱子的左边界,再从右往左遍历得到每个柱子的右边界,步骤如下图所示(为了便于理解,图中栈的元素也存放了高度,即(h, j))。然后求出每个柱子对应的最大矩形,返回其中最大的。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left, right = [0] * n, [0] * n
stack = []
for i in range(n):
while stack and heights[i] <= heights[stack[-1]]:
stack.pop()
left[i] = stack[-1] if stack else -1
stack.append(i)
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[i] <= heights[stack[-1]]:
stack.pop()
right[i] = stack[-1] if stack else n
stack.append(i)
res = max([(right[i] - left[i] - 1) * heights[i] for i in range(n)]) if n > 0 else 0
return res
二、最小栈
此题为leetcode第155题
思路:定义一个最小值self.min,在每轮push的时候更新最小值,然后将最小值连通x一起push到栈中,这样在pop的时候直接取栈顶更新最小最即可。
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.min = float('inf')
def push(self, x: int) -> None:
if self.min > x:
self.min = x
self.stack.append((x, self.min))
def pop(self) -> None:
self.stack.pop()
if self.stack:
self.min = self.getMin()
else:
self.min = float('inf')
def top(self) -> int:
if self.stack:
return self.stack[-1][0]
def getMin(self) -> int:
if self.stack:
return self.stack[-1][1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
三、滑动窗口最大值
此题为leetcode第239题
思路:维护一个双端队列q,队列里的数是数组元素的索引。遍历数组,每次循环做三件事情:(1)维护队列的单调性,我们希望队列的最左端是当前窗口的最大值,往右依次递减,因此如果nums[i] > nums[q[-1]]时就要依次弹出队列右边的元素;(2)维护队列的范围,我们希望队列的长度为k,因此当q[0] < i – k + 1时依次弹出队列左边的元素,符合范围要求后将当前的索引入队;(3)当形成窗口时,即i >= k – 1时,队列左端元素即为当前窗口最大元素。
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
q = deque()
res = []
for i in range(n):
# 维护递减队列
while q and nums[q[-1]] < nums[i]:
q.pop()
# 维护队列范围
while q and q[0] < i - k + 1:
q.popleft()
q.append(i)
# 形成窗口时添加答案
if i >= k - 1:
res.append(nums[q[0]])
return res
四、函数的独占时间
此题为leetcode第636题
思路:可以用栈来模拟函数的调用。栈里的元素是函数id,当函数如果为start的话,计算一下和前一个函数的时间差,加在前一个函数上,并将当前函数id入栈;如果为end的话,同样计算一下和前一个函数(实际上为同一个函数)的时间差,加在前一个函数上,并将前一个函数出栈。
class Solution:
def exclusiveTime(self, n: int, logs: List[str]) -> List[int]:
stack = []
res = [0] * n
s = logs[0].split(':')
stack.append(int(s[0]))
prev = int(s[2])
for i in range(1, len(logs)):
s = logs[i].split(':')
if s[1] == 'start':
if len(stack) != 0:
res[stack[-1]] += int(s[2]) - prev
stack.append(int(s[0]))
prev = int(s[2])
else:
res[stack[-1]] += int(s[2]) - prev + 1
stack.pop()
prev = int(s[2]) + 1
return res
五、验证栈序列
此题为leetcode第946题
思路:我们用一个辅助栈stack,根据给定的pushed和popped模拟入栈出栈的过程,如果模拟成功的话就符合题意。遍历pushed里的元素,在当前循环里,将元素压入栈中,然后循环出栈(循环出栈的条件是stack不为空且栈顶元素等于popped[i],并且执行i += 1)。因为数组中不包含重复元素,因此每个元素在哪个位置是唯一的,保证了出栈顺序的正确性。最后外层循环结束后,如果stack为空则返回true,否则返回false。
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
n = len(pushed)
if n <= 1:
return True
stack = []
p = 0 # 指向popped
for num in pushed:
stack.append(num)
# 循环出栈
while stack and stack[-1] == popped[p]:
stack.pop()
p += 1
return not stack
未完待续