代码随想录训练营 Day49打卡 单调栈 part02
一、力扣42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
**示例 **:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
-
单调栈的维护:
栈中存储的是柱子的下标,用于计算相应的柱子高度。
栈中柱子的高度是从栈底到栈顶严格递减的(即,栈顶的柱子高度最小)。 -
处理三种情况:
情况一: 当前柱子高度小于栈顶柱子高度,直接将当前柱子的下标入栈,因为栈的顺序需要保持从小到大。
情况二: 当前柱子高度等于栈顶柱子高度,弹出栈顶元素,并用当前柱子替换栈顶元素,因为相等的情况下需要保留最右边的柱子用于计算宽度。
情况三: 当前柱子高度大于栈顶柱子高度,此时可以确定一个“凹槽”,通过弹出栈顶元素并计算其与左侧和右侧柱子之间形成的水量。
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
版本一
class Solution:
def trap(self, height: List[int]) -> int:
# 初始化单调栈(存储柱子的下标)和结果变量
stack = [0] # 初始时将第一个柱子的下标加入栈中
result = 0 # 用于累加每个凹槽的雨水体积
for i in range(1, len(height)):
# 情况一: 当前柱子高度小于栈顶柱子高度,直接入栈
if height[i] < height[stack[-1]]:
stack.append(i)
# 情况二: 当前柱子高度等于栈顶柱子高度,替换栈顶
elif height[i] == height[stack[-1]]:
stack.pop()
stack.append(i)
# 情况三: 当前柱子高度大于栈顶柱子高度,处理凹槽并计算雨水体积
else:
while stack and height[i] > height[stack[-1]]:
# 弹出栈顶元素作为凹槽的底部(即最矮的柱子)
mid_height = height[stack[-1]]
stack.pop()
if stack:
# 栈顶元素作为左侧柱子,当前元素作为右侧柱子
left_height = height[stack[-1]]
right_height = height[i]
# 计算凹槽的高度: 两侧较矮的柱子减去凹槽底部的高度
h = min(left_height, right_height) - mid_height
# 计算凹槽的宽度: 右侧柱子的下标减去左侧柱子的下标减1
w = i - stack[-1] - 1
# 累加当前凹槽的雨水体积
result += h * w
# 当前柱子的下标入栈
stack.append(i)
return result
版本二 精简
class Solution:
def trap(self, height: List[int]) -> int:
# 初始化栈,存放柱子的下标
stack = [0]
# 初始化结果变量,用于存储总的雨水体积
result = 0
# 从下标1开始遍历柱子
for i in range(1, len(height)):
# 当当前柱子高度大于栈顶柱子高度时,处理凹槽
while stack and height[i] > height[stack[-1]]:
# 弹出栈顶元素,该元素是凹槽的底部高度
mid_height = stack.pop()
# 检查栈是否为空,若不为空则说明可以形成凹槽
if stack:
# 栈顶元素为左侧柱子的高度,当前遍历到的i为右侧柱子的高度
left_height = height[stack[-1]]
right_height = height[i]
# 计算凹槽的高度: 左右两侧柱子中较矮者 - 凹槽底部的高度
h = min(left_height, right_height) - height[mid_height]
# 计算凹槽的宽度: 右侧柱子的下标i - 左侧柱子的下标 - 1
w = i - stack[-1] - 1
# 累加当前凹槽的雨水体积
result += h * w
# 当前柱子的下标入栈,继续处理下一个柱子
stack.append(i)
return result
二、力扣84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
**示例 **:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
我们需要在一个柱状图(由高度数组表示)中找到面积最大的矩形。每个柱子的宽度都是1。为了实现这个目标,我们需要找到每个柱子左边和右边第一个比它矮的柱子,这样可以确定以当前柱子为高度的最大矩形的宽度。
举个栗子,如图:
三种情况:
当前柱子高度大于栈顶柱子高度:将当前柱子的索引入栈。
当前柱子高度等于栈顶柱子高度:弹出栈顶柱子的索引,并将当前柱子的索引入栈(因为等高的情况下,只需要保留最新的索引)。
当前柱子高度小于栈顶柱子高度:弹出栈顶柱子,并计算以栈顶柱子为高度的矩形面积,然后将当前柱子的索引入栈。
首尾补0:在heights数组的开头和结尾各插入一个高度为0的柱子,确保所有柱子都能被弹出并计算面积。
首先来说末尾为什么要加元素0?
如果数组本身就是升序的,例如[2,4,6,8],那么入栈之后 都是单调递减,一直都没有走 情况三 计算结果的哪一步,所以最后输出的就是0了。 如图:
结尾加一个0,就会让栈里的所有元素,走到情况三的逻辑。
开头为什么要加元素0?
如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),rigt(6),但是得不到 left。
因为 将 8 弹出之后,栈里没有元素了,那么为了避免空栈取值,直接跳过了计算结果的逻辑。
之后又将6 加入栈(此时8已经弹出了),然后 就是 4 与 栈口元素 8 进行比较,周而复始,那么计算的最后结果resutl就是0。 如图所示:
所以我们需要在 height数组前后各加一个元素0。
代码实现
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
# 在高度数组的首尾各添加一个0,这样可以确保所有柱子都能被计算到
heights.insert(0, 0)
heights.append(0)
# 初始化栈,栈底存放的是最左边的0的索引
stack = [0]
result = 0
# 遍历所有的柱子高度
for i in range(1, len(heights)):
# 情况一:当前高度大于栈顶高度,直接入栈
if heights[i] > heights[stack[-1]]:
stack.append(i)
# 情况二:当前高度等于栈顶高度,弹出栈顶并重新入栈
elif heights[i] == heights[stack[-1]]:
stack.pop()
stack.append(i)
# 情况三:当前高度小于栈顶高度,计算最大矩形面积
else:
while stack and heights[i] < heights[stack[-1]]:
mid_index = stack.pop() # 栈顶元素作为矩形的高度
left_index = stack[-1] # 栈顶的下一个元素作为左边界
right_index = i # 当前元素作为右边界
width = right_index - left_index - 1 # 计算矩形的宽度
height = heights[mid_index] # 矩形的高度
result = max(result, width * height) # 更新最大面积
stack.append(i)
return result