42. 接雨水
给定 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
(1)动态编程
向左扫描一次找到每个点右边的最大值
向右扫描一次找到每个点左边的最大值
从头开始遍历,遍历到每个点,使用该点左边的最大值和右边最大值中的较小值与当前值做差即为当前点可接的雨水。
class Solution42_1
{
//动态编程
public:
int trap(vector<int>& height)
{
int n = height.size();
if(n == 0)
return 0;
vector<int>leftmax(n);
vector<int>rightmax(n);
int ret = 0;
leftmax[0] = height[0];
rightmax[n-1] = height[n-1];
for(int i = 1;i<n;i++)
{
leftmax[i]= max(leftmax[i-1],height[i]);
}
for(int i = n - 2;i>=0;i--)
{
rightmax[i]= max(rightmax[i+1],height[i]);
}
for(int i = 0;i<n;i++)
{
ret += min(leftmax[i],rightmax[i])-height[i];
}
return ret;
}
};
(2)单调栈
使用一个单调栈来存储下标,该栈内的下标对应的元素按照从大到小排列
每遍历到一个点,如果当前元素大于栈顶所对应的元素,则先将栈顶元素出栈,如果剩余为空,则表示栈顶元素左边没有元素了,不能储存雨水,则继续进栈,如果不为空,则当前栈顶元素肯定比已出栈的栈顶元素大,则计算当前栈顶元素坐标的元素与当前元素和已出栈的栈顶元素三个元素之间所能储存的雨水,然后继续判断更新后的栈顶元素与当前元素的大小并计算,直到不满足当前元素小于栈顶元素。
以 4 3 2 0 1 1 5为例,前四个元素都是从大到小排列的,只需索引进栈。
到元素1时,由于当前栈顶元素为0,则需要0出栈,同时按上面步骤,计算2 0 1 所能接的雨水,把2 01的图像看成水桶,则0是桶底,2和1是构成桶的侧面板,则能接多少雨水是有1决定的,高度min(2,1)-0 = 1,宽度为元素1和元素2之间的距离,高度*宽度 = 雨水,累加到雨水值。当前元素索引进栈,此时栈内元素为 4 3 2 1的索引。
到下一个1时,不满足当前元素大于栈顶元素,直接进栈。当前栈内元素为4 3 2 1 1的索引,不计算雨水。
到下一个元素为5时,栈顶元素为1,栈顶元素出栈,计算5 1 1所构成的桶能接的雨水,min(5,1)-1 =0;
栈顶元素为1,前一元素为2,计算2 1 5 所能接的雨水,高度为min(5,2) - 1 =1,宽度为元素5和元素2之间的距离。
后边依照此规律进行。
题库连接:https://leetcode-cn.com/problems/trapping-rain-water/solution/yi-miao-jiu-neng-du-dong-de-dong-hua-jie-o9sv/
//单调栈
int trap(vector<int>& height) {
int ans = 0;
stack<int> stk; //维护一个单调栈
int n = height.size();
for (int i = 0; i < n; ++i) {
//当当前元素大于栈顶元素时,将小于当前栈顶元素不断出栈 直到栈顶元素大于当前元素,而栈顶元素每出栈一次 就要计算一次栈顶元素与前一元素能接的雨水
while (!stk.empty() && height[i] > height[stk.top()]) {
int top = stk.top();
stk.pop();
if (stk.empty()) {
break;
}
int left = stk.top();
int currWidth = i - left - 1;
int currHeight = min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stk.push(i);
}
return ans;
}
(3)双指针
当前元素能接多少雨水,取决于左边元素最大值和右边元素最大值中较小的那个
建立两个指针,分别从左向右遍历和从右向左遍历。
从左向右遍历时,找左边元素最大值,并且左边最大值小于右边最大值时,左边最大值一定小于右边任意的最大值,所以左边最大值为短板,接多少雨水右左边最大值决定,处理left下标,并自增left下标。
右边同理。
题解链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/327718
int trap(vector<int>& height)
{
int left = 0, right = height.size() - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left <= right) {
if (left_max < right_max)
{
ans +=max(0,left_max - height[left]);
left_max = max(left_max,height[left]);
++left;
}
else {
ans +=max(0,right_max - height[right]);
right_max = max(right_max,height[right]);
--right;
}
}
return ans;