重点:重点在于按照列算还是按照行算!!
(1)双指针:按每一列的雨水量算。O(n^2)时间复杂度。超时。(列算)
(2)动态规划:优化版双指针,先算出左右柱子最大高度,然后直接调用,复杂度O(n)。
(3)单调栈:复杂度O(n),难理解。建立动规。(行算)
难度困难
给定 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
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
解析:
方法1:双指针(暴力搜索)
按照每一列的雨水量来算,那么遍历柱子。对于当前的柱子i,找到左右两边最高的柱子,那么当前柱子能存的与水量为min(h1, h2)-height[i]。注意第一个柱子和最后一个柱子不接水。但过不了,超时。
方法2:动态规划
双指针方法每次都找左右最低的柱子,这样重复计算。可以利用动态规划首先计算出每个柱子左右最低柱子高度。这样复杂度O(n)。
方法3:单调栈
单调栈问题,按照行算。
首先,依次读入数组中的元素,前面若干个0直接去掉,直到读到一个大于0的,将其入栈。
对于当前的柱子高度height[i]和栈顶元素柱子高度top,同样有两种情况:
(1)当前柱子高度 <=栈顶元素柱子高度:直接入栈;
(2)当前柱子高度 =栈顶元素柱子高度:出栈栈顶,然后入栈;
(2)当前柱子高度 > 栈顶元素柱子高度:栈顶元素依次出栈,同时算出面积;
第二种情况需要注意。如果当前栈顶元素跟入栈元素一样大,那么前面那个柱子就没用了。
需要注意,栈内的元素肯定是从栈顶到栈底递增,即栈底的最大。
详细注解见:代码随想录 (programmercarl.com)
代码:
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0;
vector<int> maxLeft(height.size(), 0);
vector<int> maxRight(height.size(), 0);
int num=0;
// 记录每个柱子左边柱子最大高度
for(int i=1;i<height.size();i++)
maxLeft[i]=max(maxLeft[i-1],height[i-1]);
// 记录每个柱子右边柱子最大高度
for(int i=height.size()-2;i>=0;i--)
maxRight[i]=max(maxRight[i+1],height[i+1]);
//第一根柱子和最后一根不接水
//计算方法:左边最高的和右边最高的的最小值,减去当前柱子高度,宽度为1
//按列算,宽度默认为1,只算高度
for(int i=1;i<height.size()-1;i++){
int tmp=min(maxLeft[i],maxRight[i])-height[i];
if(tmp>0)
num+=tmp;
}
return num;
}
};
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0;
stack<int> stk;
int sum=0;
while(!stk.empty())
stk.pop();
stk.push(0);
//三种情况
for(int j=1;j<height.size();j++){
//当前元素小于栈顶,直接入栈
if(height[j]<height[stk.top()])
stk.push(j);
//当前元素等于栈顶,出栈栈顶再入栈
else if(height[j]==height[stk.top()]){
stk.pop();
stk.push(j);
}
//当前元素小于栈顶,算面积
else{
while(!stk.empty()&&height[j]>height[stk.top()]){
int mid=stk.top();
stk.pop();
if(!stk.empty()){
int h = min(height[stk.top()], height[j]) - height[mid];
int w = j - stk.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
stk.push(j);
//此处有个小重点,可以看到如果栈内只有一个元素的话,出栈之后栈为空了,取不到左边凹槽
//那个元素就直接被出栈,没派上用场,因为新入栈的当前元素比它高,挡住了它,它就没用了
}
}
return sum;
}
};