方法一:模拟
开始想到【盛最多水的容器】这个题,试了一下发现没有办法借用它的思路。后面想了想,先找到最高的柱子,再分别向左和向右找第二高的柱子,计算中间接的水,然后继续分别向左向右……思路是可行的,时间复杂度也是极高的,左右都是双层循环,跑出了577ms的耗时【望天】不过也是过了,你就说过没过吧。
class Solution {
public int trap(int[] height) {
int n = height.length;
int ans = 0;
//求出最高的柱子所在位置
int mx = 0, pos = 0;
for(int i = 0; i < n; i++) {
if(height[i] >= mx) {
mx = height[i];
pos = i;
}
}
//处理左边的部分
int mx1 = 0, pos1 = 0;
int mx2 = mx, pos2 = pos; //pos2更靠近中心
while(pos2 > 0) {
mx1 = 0;
pos1 = 0;
for(int i = pos2 - 1; i >= 0; i--) {
if(height[i] > mx1) {
mx1 = height[i];
pos1 = i;
}
}
ans += (pos2 - pos1) * mx1;
for(int i = pos1; i < pos2; i++) {
ans -= height[i];
}
mx2 = mx1;
pos2 = pos1;
}
//处理右边的部分
mx2 = mx;
pos2 = pos;
while(pos2 < n - 1) {
mx1 = 0;
pos1 = n-1;
for(int i = pos2 + 1; i < n; i++) {
if(height[i] > mx1) {
mx1 = height[i];
pos1 = i;
}
}
ans += (pos1 - pos2) * mx1;
for(int i = pos2 + 1; i <= pos1; i++) {
ans -= height[i];
}
mx2 = mx1;
pos2 = pos1;
}
return ans;
}
}
方法二:动态规划
开两个数组,leftMax[]和rightMax[],leftMax[i] 表示 0 到 i 的位置 height 的最大高度,rightMax[i] 表示 i 到 n-1 的位置 height 的最大高度。因此,我们只需要正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。两次遍历结束后,下标 i 处能接的雨水量等于 min(leftMax[i], rightMax[i]) - height[i] 。遍历每个下标位置即可得到能接的雨水总量。
Java版本
class Solution {
public int trap(int[] height) {
int n = height.length;
int leftMax[] = new int[n];
leftMax[0] = height[0];
for(int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i-1], height[i]);
}
int rightMax[] = new int[n];
rightMax[n-1] = height[n-1];
for(int i = n-2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i+1], height[i]);
}
int ans = 0;
for(int i = 0; i < n; i++) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
Python版本
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
leftMax = [height[0]] + [0] * (n - 1)
for i in range(1, n):
leftMax[i] = max(leftMax[i-1], height[i])
rightMax = [0] * (n-1) + [height[n-1]]
for i in range(n - 2, -1, -1):
rightMax[i] = max(rightMax[i+1], height[i])
ans = 0
for i in range(n):
ans += min(leftMax[i], rightMax[i]) - height[i]
return ans
-
时间复杂度:O(n),其中 n 是数组 height 的长度。计算数组 leftMax 和 rightMax 的元素值各需要遍历数组 height 一次,计算能接的雨水总量还需要遍历一次。
-
空间复杂度:O(n),其中 n 是数组 height 的长度。需要创建两个长度为 n 的数组 leftMax 和 rightMax。
方法三:双指针
在方法二里,下标 i 处能接的雨水量由 leftMax[i] 和 rightMax[i] 中的最小值决定,我们可以直接使用两个指针 left 和 right 和两个变量 leftMax 和 rightMax 代替两个数组。
我们使用 height[left] 和 height[right] 的值更新 leftMax 和 rightMax 的值。如果 height[left] < height[right] ,则必有 leftMax < rightMax,下标 left 处能接的雨水量等于 leftMax−height[left]。反之如果 height[left] ≥ height[right],则必有 leftMax ≥ rightMax,下标 right 处能接的雨水量等于 rightMax−height[right]。
当两个指针相遇时,遍历结束,得到最后答案。
Java版本
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while(left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if(height[left] < height[right]) {
ans += leftMax - height[left];
left++;
} else {
ans += rightMax - height[right];
right--;
}
}
return ans;
}
}
Python版本
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
left, right = 0, len(height) - 1
leftMax = rightMax = 0
while left < right:
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])
if(height[left] < height[right]):
ans += leftMax - height[left]
left += 1
else:
ans += rightMax - height[right]
right -= 1
return ans
-
时间复杂度:O(n),其中 n 是数组 height 的长度。两个指针的移动总次数不超过 n。
-
空间复杂度:O(1)。只需要使用常数的额外空间。
总结
方法一写下来只是记一个思路,方法二动态规划……万物皆可DP(确信),方法三是在方法二的基础上优化了空间,思路没太大变化,但是把空间复杂度由O(n)降到O(1)。
官方题解下还有一个单调栈的方法,用单调栈计算能接的雨水总量。感觉有点难理解并且难想到,自己也是一知半解,就不写在这里了。感兴趣的可以自己去看看。
265

被折叠的 条评论
为什么被折叠?



