题目如图所示:
对于该题,从以下几个方法展开求解。
1.暴力求解
直接按问题描述进行。对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。
时间复杂度为O(n2),空间复杂度为O(1)。
class Solution:
def trap(self, height: List[int]) -> int:
length = len(height)
res = 0
for i in range(1, length - 1):
max_left = 0
max_right = 0
for j in range(0, i+1):
max_left = max(height[j], max_left)
for k in range(i, length):
max_right = max(height[k], max_right)
res += (min(max_right, max_left) - height[i])
return res
改暴力求解并不能通过,时间超时。但如果对内部的两个for循环进行切片数组求最大值操作,则可以成功通过。代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
length = len(height)
res = 0
for i in range(1, length - 1):
max_left = 0
max_right = 0
max_left = max(height[:i+1])
max_right = max(height[i:])
res += (min(max_right, max_left) - height[i])
return res
运行时间大概为1816ms,时间还是很长,但相比上一个代码,已经可以成功提交了。
2.动态编程
在暴力方法中,我们仅仅为了找到最大值每次都要向左和向右扫描一次。但是我们可以提前存储这个值。因此,可以通过动态编程解决。
1.时间复杂度为O(n):
存储最大高度数组,需要两次遍历,每次 O(n),
2.空间复杂度为O(n):
使用了额外的 O(n)空间用来放置 left_max 和 right_max 数组。
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
res = 0
length = len(height)
left_max = [0 for _ in range(length)]
right_max = [0 for _ in range(length)]
left_max[0] = height[0]
for i in range(1, length):
left_max[i] = max(left_max[i-1], height[i])
right_max[length - 1] = height[length - 1]
for i in range(length - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
for i in range(1, length - 1):
res += min(left_max[i], right_max[i]) - height[i]
return res
3.栈的应用
我们可以不用像方法 2 那样存储最大高度,而是用栈来跟踪可能储水的最长的条形块。使用栈就可以在一次遍历内完成计算。
我们在遍历数组时维护一个栈。如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到 res。
#应用栈
class Solution:
def trap(self, height: List[int]) -> int:
res = 0
length = len(height)
if length < 3: return 0
index = 0
stack = []
while index < length:
while len(stack) > 0 and height[index] > height[stack[-1]]:
top = stack.pop()
if len(stack) == 0:
break
w = index - stack[-1] - 1
h = min(height[index], height[stack[-1]]) - height[top]
res += (w*h)
stack.append(index)
index += 1
return res
1.时间复杂度O(n):
单次遍历O(n),每个条形最多访问两次(由于栈的弹入和弹出),并且弹入和弹出栈都是O(1)的。
2.空间复杂度为O(n):
栈最多在阶梯型或平坦型的条形块结构中占用O(n)的空间。
4.双指针
和方法 2 相比,我们不从左和从右分开计算,我们想办法一次完成遍历。
我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
我们必须在遍历时维护left_max 和 right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。
#双指针
class Solution:
def trap(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
res = 0
left_max, right_max = 0, 0
while l < r:
if height[l] < height[r]:
if height[l] >= left_max:
left_max = height[l]
else:
res += left_max - height[l]
l += 1
else:
if height[r] >= right_max:
right_max = height[r]
else:
res += right_max - height[r]
r -= 1
return res
1.时间复杂度O(n):单次遍历的时间O(n);
2.空间复杂度O(1):l,r,left_max,right_max只需要常数的空间。
class Solution:
def trap(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
res = 0
temp_h = 0
while l <= r:
min_h = min(height[l], height[r])
if min_h > temp_h:
res += (min_h - temp_h) * (r - l + 1)
temp_h = min_h
while l <= r and height[l] <= temp_h:
l += 1
while l <= r and height[r] <= temp_h:
r -= 1
res -= sum(height)
return res