主要参考思路
全文围绕一个定理展开:在某个位置
i
i
i 处,它能存的水,取决于它左右两边的最大值中较小的一个
JAVA
① 按行求【超时】
class Solution {
public int trap(int[] height) {
int len = height.length;
int max_height = getMax(height); // 获取最高层数
int ans = 0; // 总储水量
for (int i = 1; i <= max_height; ++i) {
int temp = 0; // 临时储水量
boolean isStart = false;
for (int j = 0; j < len; ++j) {
if (isStart && height[j] < i) {
temp++;
}
if (height[j] >= i) {
ans += temp;
temp = 0;
isStart = true;
}
}
}
return ans;
}
private int getMax(int[] height) {
int len = height.length;
int ans = 0;
for (int i = 0; i < len; ++i) {
ans = Math.max(ans, height[i]);
}
return ans;
}
}
但这种代码在下面这种情况下会出错
实际答案是
1
1
1,但因为数组最右边多出了
0
0
0,因此实际输出会变成
2
2
2
② 按列求(动态规划)【时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( N ) O(N) O(N)】
class Solution {
public int trap(int[] height) {
int len = height.length;
int[] left_max = new int[len]; // 当前位置往左边墙最大的高度
int[] right_max = new int[len]; // 当前位置往右边墙最大的高度
for (int i = 1; i <= len - 2; ++i) {
left_max[i] = Math.max(left_max[i - 1], height[i - 1]); // left_max[0]自然是 0
}
for (int i = len - 2; i >= 1; --i) {
right_max[i] = Math.max(right_max[i + 1], height[i + 1]); // right_max[len - 1]自然是 0
}
int ans = 0;
for (int i = 1; i <= len - 2; ++i) {
int temp_min = Math.min(left_max[i], right_max[i]);
if (temp_min > height[i]) {
ans += temp_min - height[i];
}
}
return ans;
}
}
③ 双指针【时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( 1 ) O(1) O(1)】
class Solution {
public int trap(int[] height) {
int ans = 0;
int len = height.length;
int left_max = 0;
int right_max = 0;
int left = 1;
int right = len - 2;
for (int i = 1; i <= len - 2; ++i) {
if (height[left - 1] < height[right + 1]) {
left_max = Math.max(left_max, height[left - 1]);
if (height[left] < left_max) {
ans += left_max - height[left];
}
left++;
} else {
right_max = Math.max(right_max, height[right + 1]);
if (height[right] < right_max) {
ans += right_max - height[right];
}
right--;
}
}
return ans;
}
}
为什么不一开始判断时用 left_max < right_max
?
答:因为一开始两者都等于 0,无法进行判断,而 height[left - 1]
是可能成为 left_max
的变量, 同理,height [right + 1]
是可能成为 right_max
的变量,只要保证 height[left - 1] < height[right + 1]
,那么 left_max
就一定小于 right_max
,因此我们可以用 height[left - 1] < height[right + 1]
代替 left_max < right_max
④ 单调栈【时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( N ) O(N) O(N)】
import java.util.Deque;
import java.util.LinkedList;
public class Solution {
public int trap(int[] height) {
if (height == null) {
return 0;
}
Deque<Integer> stack = new LinkedList<>();
int ans = 0;
for (int i = 0; i < height.length; i++) {
while(!stack.isEmpty() && height[stack.peek()] < height[i]) {
int curIdx = stack.pop();
// 如果栈顶元素一直相等,那么全都pop出去,只留第一个。
while (!stack.isEmpty() && height[stack.peek()] == height[curIdx]) {
stack.pop();
}
if (!stack.isEmpty()) {
int stackTop = stack.peek();
// stackTop此时指向的是此次接住的雨水的左边界的位置。右边界是当前的柱体,即i。
// Math.min(height[stackTop], height[i]) 是左右柱子高度的min,减去height[curIdx]就是雨水的高度。
// i - stackTop - 1 是雨水的宽度。
ans += (Math.min(height[stackTop], height[i]) - height[curIdx]) * (i - stackTop - 1);
}
}
stack.add(i);
}
return ans;
}
}
单调栈参考文章
虽然但是,我还是看不懂啊!
需要注意的地方是不直接使用 S t a c k Stack Stack,而使用 D e q u e Deque Deque 代替 S t a c k Stack Stack
Python
① 按列求(动态规划)(48ms)
class Solution:
def trap(self, height: List[int]) -> int:
length=len(height)
ans=0
left_max=[0]*length # 相当于初始化数组
right_max=[0]*length
for i in range(1,length-1):
left_max[i]=max(height[i-1],left_max[i-1])
for i in range(length-2,0,-1): # 加个 -1表示倒着遍历
right_max[i]=max(height[i+1],right_max[i+1])
for i in range(1,length-1):
tmp_max=min(left_max[i],right_max[i])
if tmp_max>height[i]:
ans+=tmp_max-height[i]
return ans
② 双指针(44ms)
class Solution:
def trap(self, height: List[int]) -> int:
length=len(height)
ans=0
left_max,right_max=0,0
left,right=1,length-2
for i in range(1,length-1): # 可以替换成 while left<=right:
if height[left-1]<height[right+1]:
left_max=max(left_max,height[left-1])
if left_max>height[left]:
ans+=left_max-height[left]
left+=1
else:
right_max=max(right_max,height[right+1])
if right_max>height[right]:
ans+=right_max-height[right]
right-=1
return ans
③ 单调栈(44ms)
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
if n == 0:
return 0
stack = []
ans = 0
for i in range(n):
while stack and stack[-1][0] < height[i]:
tmp = stack.pop() # 删除的是最右边的元素,因此可作为堆使用
while stack and stack[-1][0] == tmp[0]:
stack.pop()
if stack:
ans += (min(stack[-1][0], height[i])-tmp[0])*(i-stack[-1][1]-1)
stack.append([height[i], i])
return ans
本代码的
s
t
a
c
k
stack
stack 是二维的,一维代表索引,二维的 stack[i][0]
代表墙高度,stack[-1][1]
代表下标
判断
s
t
a
c
k
stack
stack 是不是空不需要使用函数,只用 if stack
即可