单调栈整理
单调栈
最近做题,遇到不少单调栈的问题,整理一下;
参考博客 coordinate_blog ;
单调栈需要处理问题的情形是回答,比当前元素更大/更小的下一个或者前一个数;
leetcode. 42 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水?
注意这个单调栈的写法,之前自己写得是不是太复杂,为什么最后stack非空的情况不需要考虑?是因为这时候中间的部分都已经算过了
class Solution:
def trap(self, height: List[int]) -> int:
stack = []
res = 0
for i in range(len(height)):
while stack and height[stack[-1]]<height[i]:
tmp = stack.pop()
if not stack:
break
res += (min(height[stack[-1]],height[i])-height[tmp])*(i-stack[-1]-1)
stack.append(i)
return res
其实自己差不多已经写出来了,但是中间的多了很多不必要的考虑。还是在动手写写之前没有考虑清楚就下手,最为致命;
还是你的解题思路不对,没有想清楚就下手,必须逼迫自己按照合理的解题思路写题;
class Solution:
def trap(self, height: List[int]) -> int:
stack = []
res = 0
for i in range(len(height)):
if not stack or height[stack[-1]]>=height[i]:
stack.append(i)
else:
while stack and height[stack[-1]]<height[i]:
tmp = stack.pop()
if not stack:
break
h = min(height[stack[-1]],height[i]) - height[tmp]
w = i-stack[-1]-1
res += h*w
stack.append(i)
return res
leetcode 496. 给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。
最容易想到的解法
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = []
for v in nums1:
indx = nums2.index(v)
stack = []
flag = 0
for u in nums2[indx:]:
if u > v:
flag = 1
res.append(u)
break
if not flag:
res.append(-1)
return res
单调栈的写法,很有趣,参考:coordinate_blog
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
stack = []
dic = dict()
for v in nums2:
while stack and stack[-1]<v:
dic.setdefault(stack.pop(),v)
stack.append(v)
return [dic.get(v,-1) for v in nums1]
leetcode 503. 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
判断循环,这里可以直接复制一遍;单调栈的应用
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums = nums + nums
stack = []
dic = dict()
for i in range(len(nums)):
while stack and nums[stack[-1]]<nums[i]:
tmp = stack.pop()
dic.setdefault(tmp,nums[i])
stack.append(i)
return [dic.get(i,-1) for i in range(len(nums)//2)]
leetcode 739. 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
dic = dict()
stack = []
for i in range(len(T)):
while stack and T[stack[-1]]<T[i]:
tmp = stack.pop()
dic[tmp] = i-tmp
stack.append(i)
return [dic.get(i,0) for i in range(len(T))]
leetcode 901. 编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。
还是太懒,不舍得动脑子;
class StockSpanner:
def __init__(self):
self.stack = [(float("inf"),0)]
self.count = 0
def next(self, price: int) -> int:
self.count += 1
while self.stack and self.stack[-1][0] <= price:
self.stack.pop()
res = self.count - self.stack[-1][1]
self.stack.append((price,self.count))
return res
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)
leetcode 239. 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
自己写的,不够简洁
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums:
return
if len(nums)<=k:
return [max(nums)]
stack = nums[:k]
res = [max(stack)]
for i in range(k,len(nums)):
stack = stack[1:]+[nums[i]]
res.append(max(stack))
return res
同样的思路,别人的写法,简洁,清晰;参考: 博客
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums:
return
res = list()
for i in range(len(nums)-k+1):
res.append(max(nums[i:i+k]))
return res
单调栈的写法,比较精妙,参考:博客
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums:
return list()
stack, res = list(), list()
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]:
stack.pop()
stack.append(i)
if i-stack[0]>=k:
stack.pop(0)
if i>k-2:
res.append(nums[stack[0]])
return res
leetcode 84. 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
这道题是第三遍做,还是没有一下子写出来,做题不是凭记忆,而是要理解思路,考虑细节
思路是,当下一个值比栈顶元素小时,说明该栈顶元素为矩形的右边界,这时候需要弹栈操作;另一个细节就是需要在末尾插入一个数值,以便将栈里的元素都弹出来;
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
if not heights:
return 0
stack = []
res = 0
heights.append(0) ##特别是这个操作,很关键
for i in range(len(heights)):
while stack and heights[i] <= heights[stack[-1]]:
tmp = stack.pop()
h = heights[tmp]
w = i if not stack else i-stack[-1]-1
res = max(res,h*w)
stack.append(i)
return res