单调栈也是笔试面试的时候经常出现的考点,经典的题目如Leetcode739.每日温度 与 Leetcode42.接雨水,这两个题均是维护一个单调栈
注意:
1)题目一般给的是一个数组,那么栈中存放的可以是数组的下标,也可以是数组元素,这里要区分好。
2)根据题意看是要维护一个单调递增还是单调递减的栈,上述两题均是维护一个单调递减栈
伪代码:
stack = [] # 创建一个栈,并维护其形成一个单调栈
ans = [0]*len(数组) or ans = 0 # 看结果需要返回的是一个数还是一个列表,这里以返回一个列表举例
for i in range(len(数组)):
while stack and 数组[i] > 数组[stack[-1]]: # 如果栈非空并且栈顶元素小于数组索引i处元素
prev_index = stack.pop() # 将元素出栈
ans[pre_index] = i - pre_index
stack.append(i) # 此时符合单调栈,将i索引值推入栈顶
print(ans)
Leetcode739.每日温度 题目链接
def dailyTemperatures(temperatures: List[int]) -> List[int]:
# 单调栈:时间复杂度O(n),空间复杂度O(n)
# 遍历每日温度,维护一个单调栈:
# 若栈为空或者当日温度小于等于栈顶温度则直接入栈
# 反之,若当日温度大于栈顶温度,说明栈顶元素的升温日找到了,则将栈顶元素出栈,计算其与当日相差的天数即可。
length = len(temperatures)
stack = [] # 栈
res = [0]*length # 存放结果
for i in range(length):
temperature = temperatures[i] # 当日温度
while stack and temperature> temperatures[stack[-1]]: # 如果当日温度大于栈顶温度,说明找到该日的升温日了
preIndex = stack.pop()
res[preIndex] = i-preIndex
stack.append(i)
return res
Leetcode42.接雨水
解法一:单调栈
建议debug一下,会发现,单调栈解法中,code计算ans值是一层一层计算的,如下图所示
def trap(height: List[int]) -> int:
# 设计一个单调栈,单调栈中存储的是下标,满足从栈底到栈顶的下标对应的数组height中的元素递减,单调递减栈
ans = 0 # 记录结果
stack = [] # 单调栈
for i,h in enumerate(height): # 同时获得下标和对应的高度
while stack and h> height[stack[-1]]:# 可以接到雨水的区域一定是高度先减后增的,在单调栈中,栈顶元素永远比它的前一个元素低
#此时的高度有比栈顶元素高,这样就是一个高低高的区域,故可以得到一个可以接雨水的区域
top = stack.pop()
if not stack: # 栈为空,左边不存在最大值,无法接到雨水
break
left = stack[-1] # left成为新的栈顶元素
currWidth = i-left-1 #获取接雨水区域的宽度
currHeight = min(height[left], height[i])- height[top] # 能接到雨水的高度
ans += currWidth*currHeight
stack.append(i) # 在对下标i出计算能接的雨水量之后,将i入栈,继续遍历后面的下标
return ans
解法二:找到最高点二分计算左右区间面积
针对最高处左侧的区域,我们可以得出以下结论:能接到雨水的面积只与左边部分有关,如果左半部分比现在的位置高,那么一定能收集到雨水
针对最高处右侧的区域,我们可以得出以下结论:能接到雨水的面积只与右部分有关,如果右半部分比现在的位置高,那么一定能收集到雨水
def trap(height: List[int]) -> int:
# 找到数组最大值及其下标,作为边界,将数组分为两份
max_value = max(height)
max_index = height.index(max_value) # python 中 a.index(target) 可以返回某一个值的索引
# print(max_value,max_index)
result = 0
# 处理左半部分
lst1 = height[0:max_index+1] # [0,1,0,2,1,0,1,3]
for left in range(0,max_index): # 到不了max_index
for i in range(left,max_index+1): # 到得了max_index
if lst1[left]>lst1[i]:
result+= lst1[left]-lst1[i]
else:
left = i
break
lst2 = height[max_index:]
lst2 = lst2[::-1] # 将右半部分反转,这样和左半部分求解相同
for left in range(0,len(lst2)-1): # 到不了max_index
for i in range(left,len(lst2)): # 到得了max_index
if lst2[left]>lst2[i]:
result+= lst2[left]-lst2[i]
else:
left = i
break
return result