503. 下一个更大元素 II
思路:
这道题和739. 每日温度 (opens new window)也几乎如出一辙。
不过,本题要循环数组了。
本篇我侧重与说一说,如何处理循环数组。
相信不少同学看到这道题,就想那我直接把两个数组拼接在一起,然后使用单调栈求下一个最大值不就行了!
确实可以!
将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。
其实也可以不扩充nums,而是在遍历的过程中模拟走了两遍nums。
代码:
通过取模,模拟走了两遍nums
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
answer = [-1]*len(nums)
stack = [0]
for i in range(1,len(nums)*2):
# 情况一和情况二,当前遍历元素小于等于栈顶中下标对应的元素
if nums[i%len(nums)]<=nums[stack[-1]]: # 通过取模来实现成环(i到结尾之后再转一圈回来)
stack.append(i%len(nums))
# 情况三,当前遍历元素大于栈顶中下标对应的元素
else:
while len(stack) != 0 and nums[i%len(nums)]>nums[stack[-1]]:
answer[stack[-1]]=nums[i%len(nums)]
stack.pop()
stack.append(i%len(nums))
return answer
单调栈的精简版本,即三种情况都做了合并的操作
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
answer = [-1]*len(nums)
stack = []
for i in range(len(nums)*2):
while len(stack) != 0 and nums[i%len(nums)]>nums[stack[-1]]:
answer[stack[-1]]=nums[i%len(nums)]
stack.pop()
stack.append(i%len(nums))
return answer
把两个数组拼接
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums2 = nums[:]
for i in nums:
nums2.append(i)
answer = [-1]*len(nums2)
stack = [0]
for i in range(1,len(nums2)):
# 情况一和情况二,当前遍历元素小于等于栈顶中下标对应的元素
if nums2[i]<=nums2[stack[-1]]:
stack.append(i) # 直接将当前元素的下标加入栈中
# 情况三,当前遍历元素大于栈顶中下标对应的元素
else:
while len(stack) != 0 and nums2[i]>nums2[stack[-1]]: # while循环持续比较当前遍历元素和栈顶下标对应的元素,直到栈为空 或 当前遍历元素小于等于栈顶下标对应的元素
answer[stack[-1]]=nums2[i] # 收获结果,对answer数组下标(即栈顶下标)对应的元素赋值,值为 当前遍历的元素
stack.pop() # 弹出栈顶元素(因为已经使用过了)
stack.append(i) # while循环之后,将当前遍历的元素的下标入栈
return answer[:len(nums)]
42. 接雨水
思路:
单调栈解法:
单调栈就是保持栈内元素有序。和栈与队列:单调队列 (opens new window)一样,需要我们自己维持顺序,没有现成的容器可以用。
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积。
那么本题使用单调栈有如下几个问题:
1.首先单调栈是按照行方向来计算雨水,如图:
知道这一点,后面的就可以理解了。
2.使用单调栈内元素的顺序
从大到小还是从小到大呢?
从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
3.遇到相同高度的柱子怎么办。(这步可以省略,但是可以优化时间复杂度)
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
如图所示:
ps:问:先弹出再入栈,在计算的时候取到的索引值会变化吧,计算的宽度就会减少。例如 [60,20,20,10,30]这种情况
答:不会减少,因为减法的下标是两边高的柱子的下标,不是当前柱子的下标
4.栈里要保存什么数值
使用单调栈,也是通过 长 * 宽 来计算雨水面积的。
长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,
那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢。
其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。
明确了如上几点,我们再来看处理逻辑。
单调栈处理逻辑
以下操作过程其实和 739. 每日温度 (opens new window)也是一样的,建议先做 739. 每日温度 (opens new window)。
以下逻辑主要就是三种情况
- 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
- 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
- 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]
先将下标0的柱子加入到栈中,st.push(0);
栈中存放我们遍历过的元素,所以先将下标0加进来。
然后开始从下标1开始遍历所有的柱子,for (int i = 1; i < height.size(); i++)
代码:
class Solution:
def trap(self, height: List[int]) -> int:
# 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]]: # while循环持续比较,直到当前遍历元素小于栈口元素(这里每次遍历就是 收获每个凹槽的接水量)
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 # 凹槽接水量的高=两侧的较矮一方的高度 - 凹槽底部高度
w = i - stack[-1] - 1 # 凹槽接水量的宽=凹槽右侧下标 - 凹槽左侧下标 - 1
result += h * w # 凹槽的接水量=高乘宽
stack.append(i) # 将当前遍历的元素加入栈中
return result