题目:给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5] 输出:9
这里先提供我c++时的第一种思路,后面运行时会超时
一、这是一种暴力解法,对每个柱子,计算其左右两侧最高柱子的高度,然后根据这两侧的最低高度与当前柱子的高度差,计算该柱子能接的雨水量。
-
问题核心:
每个柱子能接的雨水量取决于其左右两侧的最高柱子。具体来说,当前柱子height[i]
能接的雨水量为:雨水量=min(左最大高度,右最大高度)−当前高度(若结果为正)雨水量=min(左最大高度,右最大高度)−当前高度(若结果为正)
如果左/右最大高度中至少有一个不超过当前高度,则无法接水。
-
暴力解法的步骤:
- 遍历每个柱子
i
(首尾柱子无法接水,因此遍历范围为i = 1
到i = n-2
)。
- 遍历每个柱子
- 对每个柱子
i
:- 向左遍历:找到左侧所有柱子中的最大高度
leftMax
。 - 向右遍历:找到右侧所有柱子中的最大高度
rightMax
。 - 如果
leftMax
或rightMax
中至少有一个不超过height[i]
,则跳过(无法接水)。 - 否则,计算当前柱子能接的雨水量:
min(leftMax, rightMax) - height[i]
,并累加到总和中。
class Solution { public: int trap(vector<int>& height) { int n = height.size(); int total = 0; for(int i = 1;i < n-1;i++) { int leftMax = 0; int rightMax = 0; for(int j = i-1;j >= 0;j--) { leftMax = max(leftMax,height[j]); } for(int j = i+1;j < n;j++) { rightMax = max(rightMax,height[j]); } if(leftMax <= height[i] || rightMax <= height[i]) { continue; } total += min(leftMax,rightMax) - height[i]; } return total; } };
- 向左遍历:找到左侧所有柱子中的最大高度
-
时间复杂度:O(n2)O(n2)
对于每个柱子i
,需要两次线性遍历(左遍历和右遍历),总时间复杂度为 O(n×n)=O(n2)O(n×n)=O(n2)。 -
空间复杂度:O(1)O(1)
仅使用了常数额外空间(leftMax
、rightMax
、total
等变量)。
二、双指针法
通过维护左右两个指针和左右两侧的最大高度,动态计算每个位置能接的雨水量。具体步骤如下:
-
初始化指针和变量:
left
指针从左向右移动(初始位置为1
)。right
指针从右向左移动(初始位置为n-2
)。maxLeftHeight
记录左侧已遍历的最大高度(初始为height[0]
)。maxRightHeight
记录右侧已遍历的最大高度(初始为height[n-1]
)。
-
循环条件:
- 当
left <= right
时,指针持续向中间移动,直到相遇。
- 当
-
移动指针的策略:
- 比较左右最大高度:若
maxLeftHeight < maxRightHeight
,则当前左侧的指针位置的雨水量由maxLeftHeight
决定,因此移动left
指针向右。 - 否则(
maxLeftHeight >= maxRightHeight
),当前右侧的指针位置的雨水量由maxRightHeight
决定,因此移动right
指针向左。
- 比较左右最大高度:若
-
计算雨水量:
- 当移动左指针时:
- 如果当前高度
height[left]
小于maxLeftHeight
,则该位置能接的雨水量为maxLeftHeight - height[left]
。 - 否则,更新
maxLeftHeight
为当前高度height[left]
。
- 如果当前高度
- 当移动右指针时:
- 如果当前高度
height[right]
小于maxRightHeight
,则该位置能接的雨水量为maxRightHeight - height[right]
。 - 否则,更新
maxRightHeight
为当前高度height[right]
。
- 如果当前高度
- 当移动左指针时:
c++部分
class Solution
{
public:
int trap(vector<int>& height)
{
int n = height.size();
int total = 0;
int left = 1,right = n-2;
int maxLeftHeight = height[0];
int maxRightHeight = height[n-1];
while(left <= right)
{
if(maxLeftHeight < maxRightHeight)
{
if(maxLeftHeight > height[left])
{
total += (maxLeftHeight - height[left]);
}
else
{
maxLeftHeight = height[left];
}
left++;
}
else
{
if(maxRightHeight > height[right])
{
total += (maxRightHeight - height[right]);
}
else
{
maxRightHeight = height[right];
}
right--;
}
}
return total;
}
};
c#部分
public class Solution
{
public int Trap(int[] height)
{
int n = height.Length;
int total = 0;
int left = 1,right = n-2;
int maxLeftHeight = height[0];
int maxRightHeight = height[n-1];
while(left <= right)
{
if(maxLeftHeight < maxRightHeight)
{
if(maxLeftHeight > height[left])
{
total += (maxLeftHeight - height[left]);
}
else
{
maxLeftHeight = height[left];
}
left++;
}
else
{
if(maxRightHeight > height[right])
{
total += (maxRightHeight - height[right]);
}
else
{
maxRightHeight = height[right];
}
right--;
}
}
return total;
}
}
-
时间复杂度:O(n)O(n)
每个柱子仅被访问一次,指针从两端向中间移动,总循环次数为 n−2n−2(排除首尾)。 -
空间复杂度:O(1)O(1)
仅使用了常数额外空间(指针和临时变量)。