单调栈第二天,也是本轮刷题任务倒数第二天,加油!
503.下一个更大元素II
这道题和739. 每日温度几乎如出一辙。在遍历的过程中模拟走了两遍nums。
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
stack = []
stack.append(0)
res = [-1] * len(nums)
n = len(nums)
for i in range(1, len(nums)*2):
if nums[i % n] < nums[stack[-1]]:
stack.append(i % n)
elif nums[i % n] == nums[stack[-1]]:
stack.append(i % n)
else:
while stack and nums[i % n] > nums[stack[-1]]:
res[stack[-1]] = nums[i % n]
stack.pop()
stack.append(i % n)
return res
简化版:
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
dp = [-1] * len(nums)
stack = []
for i in range(len(nums)*2):
while(len(stack) != 0 and nums[i%len(nums)] > nums[stack[-1]]):
dp[stack[-1]] = nums[i%len(nums)]
stack.pop()
stack.append(i%len(nums))
return dp
42. 接雨水
1. 暴力解法
本题暴力解法是使用双指针。
按照列来计算,比较容易理解,接下来看一下按照列如何计算。
首先,如果按照列来计算的话,宽度一定是1了,再把每一列的雨水的高度求出来就可以了。
可以看出每一列雨水的高度,取决于该列左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
class Solution:
def trap(self, height: List[int]) -> int:
sum = 0
for i in range(1,len(height)-1):
lheight = height[i]
rheight = height[i]
for j in range(i):
if lheight < height[j] :
lheight = height[j]
for j in range(i+1, len(height)):
if height[j] > rheight:
rheight = height[j]
h = min(lheight, rheight) - height[i]
if h > 0: sum += h
return sum
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。
2. 双指针优化
在暴力解法中,可以看到只要记录左边柱子的最高高度 和 右边柱子的最高高度,就可以计算当前位置的雨水面积,这就是通过列来计算。
当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算。
当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
if len(height) < 3: return 0
sum = 0
maxlheight = [0] * len(height)
maxrheight = [0] * len(height)
maxlheight[0] = height[0]
for i in range(1,len(height)):
maxlheight[i] = max(maxlheight[i-1] , height[i])
maxrheight[len(height)-1] = height[len(height)-1]
for j in range(len(height)-2,-1,-1):
maxrheight[j] = max(maxrheight[j+1] , height[j])
for i in range(len(height)):
h = min(maxlheight[i], maxrheight[i]) - height[i]
if h > 0: sum += h
return sum
3. 单调栈解法
思路:
单调栈处理逻辑
以下操作过程其实和 739. 每日温度也是一样的。。
以下逻辑主要就是三种情况
然后开始从下标1开始遍历所有的柱子,for (int i = 1; i < height.size(); i++)。
如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。
如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了
取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid]。
此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()]。
当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i]。
此时可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:int h = min(height[st.top()], height[i]) - height[mid];
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;
当前凹槽雨水的体积就是:h * w。
class Solution:
def trap(self, height: List[int]) -> int:
# 单调栈
'''
单调栈是按照 行 的方向来计算雨水
从栈顶到栈底的顺序:从小到大
通过三个元素来接水:栈顶,栈顶的下一个元素,以及即将入栈的元素
雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度)
'''
# stack储存index,用于计算对应的柱子高度
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:
right_height = height[i]
left_height = height[stack[-1]]
# 两侧的较矮一方的高度 - 凹槽底部高度
h = min(right_height, left_height) - mid_height
# 凹槽右侧下标 - 凹槽左侧下标 - 1: 只求中间宽度
w = i - stack[-1] - 1
# 体积:高乘宽
result += h * w
stack.append(i)
return result