题目链接 | 解题思路
本题和每日温度非常相似,只是需要循环数组。最简单的方法当然是直接拼接数组,然后直接使用单调栈,最后修改输出的形状即可。不过这个方法需要修改数组,有额外的空间、时间复杂度。
以下可以直接模拟走两遍数组的过程,而不需要额外的复杂度。
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
result = [-1] * len(nums)
stack = [0]
for i in range(1, len(nums) * 2):
if nums[i % len(nums)] <= nums[stack[-1]]:
stack.append(i % len(nums))
else:
while (len(stack) > 0 and nums[i % len(nums)] > nums[stack[-1]]):
result[stack[-1]] = nums[i % len(nums)]
stack.pop()
stack.append(i % len(nums))
return result
题目链接 | 解题思路
本题真的很经典、很常考!不同解法考验了不同方面的知识,真乃神题!
在开始选择解法之前,一定要先理清思路:究竟是按行还是按列来进行计算?
暴力解法实际上也是双指针。按照列来计算比较容易,此时只需要计算每一列中能够储存的雨水高度即可。每一列的雨水高度取决于
(补一张图!)
得到两者中更小的高度后,减去当前列的柱子高度就是雨水高度.
想清楚思路后很容易写出代码,要注意的是最左侧和最右侧的柱子是没办法接雨水的。
class Solution:
def trap(self, height: List[int]) -> int:
sum = 0
for i in range(1, len(height) - 1):
left_max_height = right_max_height = height[i]
for left in range(i-1, -1, -1):
if height[left] > left_max_height:
left_max_height = height[left]
for right in range(i+1, len(height)):
if height[right] > right_max_height:
right_max_height = height[right]
sum += min(left_max_height, right_max_height) - height[i]
return sum
意料之中,暴力解法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超时。
暴力解法中有大量的重复计算:在计算每一列作为谷底的时候,都要进行一次遍历来得到左侧的最大高度和右侧的最大高度。
优化:可以通过两个静态的数组来分别记录数组中每个位置的左侧最大高度、右侧最大高度,只要以 O ( n ) O(n) O(n) 的时间完成这两个记录,就能优化整体的时间复杂度。
实际的记录过程有点类似于简单的 dp。以左侧最大高度为例,
left_higher[i] = left_higher[i-1]
;left_higher[i] = height[i]
,class Solution:
def trap(self, height: List[int]) -> int:
left_higher = [0] * len(height)
left_higher[0] = height[0]
for i in range(1, len(height)):
left_higher[i] = max(left_higher[i-1], height[i])
right_higher = [0] * len(height)
right_higher[-1] = height[-1]
for i in range(len(height) - 2, -1, -1):
right_higher[i] = max(right_higher[i+1], height[i])
sum = 0
for i in range(1, len(height) - 1):
sum += min(left_higher[i], right_higher[i]) - height[i]
return sum
空间换时间的优化,此时的时间复杂度是 O ( n ) O(n) O(n),但是空间复杂度也是 O ( n ) O(n) O(n),因为记录了两个数组。
单调栈在本题中的应用思路有些模糊。一方面,单调栈适用于寻找当前元素左侧/右侧的第一个更大元素值,和本题的“寻找谷地”有紧密的联系;另一方面,之前在思路中提到的是寻找“左侧/右侧的最大高度”,而不是第一个更大元素,这似乎又没有很紧密的关系。
思路转换的突破口在于,要使用单调栈解题,应该按行计算。
按照行计算,就需要找到这一行(一个谷内的行,不是传统意义上的一行)的起始位置。而起始位置就是由这一行的左、右侧第一个更大值的位置决定的。
单调栈内的元素顺序、如何取值
当前元素与栈口元素相等:这个情况不像之前的题目一样直接,需要进行额外的讨论
栈内记录的元素:通过单调栈的更新结果来计算雨水量,需要当前行的高和宽。其中高需要列的值,宽需要列的下标,既然通过下标可以直接获取值,栈内只需要记录下标即可。
单调栈的三种情况:
height[i] < height[stack[-1]]
height[i] == height[stack[-1]]
height[i] > height[stack[-1]]
height[i] == height[stack[-1]]
时没有弹出之前的栈口元素,那么可能会遇到谷内能够接到的雨水量为 0 的情况class Solution:
def trap(self, height: List[int]) -> int:
sum = 0
stack = [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() # optional, improve performances
stack.append(i)
else:
while (len(stack) > 0 and height[i] > height[stack[-1]]):
valley_bottom = stack.pop() # store it as it is popped
if len(stack) > 0: # possibly empty now
curr_height = min(height[stack[-1]], height[i]) - height[valley_bottom]
curr_width = i - stack[-1] - 1
sum += curr_height * curr_width
stack.append(i)
return sum
单调栈的解法应该是最优的,时间复杂度是 O ( n ) O(n) O(n),空间复杂度也是 O ( n ) O(n) O(n),并且比双指针 + dp 少记录了一个数组。