42. 接雨水[hard]
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
思路
如果走到每一格,知道该格能达到最高的水位就好了。这确实是个好想法,所以该想想怎么去求每格最高水位。
一种暴力的想法是走到每一格,向左向右遍历找到该格左右两边最高的柱子,就可以了。复杂度达到O(n2),可以考虑空间换时间。
优化一下第一种方法,得到第二种,用两个数组,一个从左边到右边走,记录小池子最左边最高的柱子,一个从右边到左边走,记录小池子最右边最高的柱子,借助力扣官方题解的图来说明一下,即如下:
第二种方法借助空间把时间复杂度降到O(n),但是空间开销稍微有点多,可以再优化一下。
在第二种方法的基础上,优化空间,得到第三种方法。可以用一个数组记录小池子一边最高的柱子,最后一次计算从反方向走,一边走一边得到小池子反方向最高的柱子,即将一个数组降到一个记录某方向最大值的变量。有机整合遍历,减少开销。
可不可以将另一个数组也优化掉呢?这就需要第四种方法,即双指针。用两个变量记录两个方向路上见到的该侧最高柱子高度,移动较矮的变量,边移动边累加。
第五种方法和前四种思路不一样,需要用栈,试图将小池子的最侧先压入栈,遇到小池子右侧高柱时,横向一层层累计水量。具体效果,借用力扣windliang的题解图说明一下。
用栈实现时候,代码不是好理解,可以参考下图。
综上,前四种的思路基本一致,是立体图形左右视图的思路,需要得到或记录小池子左右两边最高柱,一列列纵向累计水量。第五种方法是横向思路,遇到小池子,从底层到高层一层层累计水量。
这里给出第三种,第四种和第五种解法的实现。
解法3:数组记从右到左看的右侧最大值,一列列计算
class Solution {
public:
int trap(vector<int>& height) {
int len = height.size();
if (len < 3)
return 0;
int sum = 0;
vector<int> rightMax(len); //记录从右到左,每格的最大值
rightMax[len-1] = height[len-1];
for (int i = len-2; i >= 0; --i)
rightMax[i] = max(height[i], rightMax[i+1]);
int leftMax = height[0]; //记录从左到右,每格的最大值
for (int i = 1; i < len; ++i) {
if (height[i] > leftMax)
leftMax = height[i];
else
sum += min(leftMax, rightMax[i]) - height[i];
}
return sum;
}
};
解法4:双指针,左右出发,对称处理
class Solution {
public:
int trap(vector<int>& height) {
int len = height.size();
if (len < 3)
return 0;
int sum = 0;
int left = 0, right = len-1;
int leftMax = 0, rightMax = 0; //记录左右方向看到的最大值
while (left < right) {
if (height[left] <= height[right]) { //左边较短,先处理左边
if (height[left] >= leftMax) //跟新最大左柱高
leftMax = height[left];
else //累计水量
sum += leftMax-height[left];
++left;
} else {
if (height[right] >= rightMax)
rightMax = height[right];
else
sum += rightMax-height[right];
--right;
}
}
return sum;
}
};
解法5:用栈,对每个小池子一行行累计,先累加底层的水,再一层层往上累加
class Solution {
public:
int trap(vector<int>& height) {
int len = height.size();
if (len < 3)
return 0;
int sum = 0;
stack<int> sta; //没遇见小池子右墙时,先把左边坑的位置压入
for (int i = 0; i < len; ++i) {
while (!sta.empty() && height[i] > height[sta.top()]) { //遇见小池子的右墙
int top = sta.top(); //池底index
sta.pop();
if (sta.empty())
break;
int high = min(height[sta.top()], height[i]) - height[top];//小池子左右墙能装的相对高度
int wide = i-sta.top()-1; //小池子当前高度跨越的格子数
sum += high*wide;
}
sta.push(i);
}
return sum;
}
};