攒青豆
当青训营遇上码上掘金。在青训营 X 码上掘金创作活动中,我选择主题4攒青豆来进行创作。这道题其实和LeetCode43一模一样。解题的思路和方法也多种多样。在这里我挑两种最经典的方法来说一下。
方法一 动态规划
创建两个长度为 的数组 和 。对于 , 表示下标 及其左边的位置中, 的最大高度, 表示下标 及其右边的位置中, 的最大高度。
显然,,。两个数组的其余元素的计算如下:
- 当 时,;
- 当 时,。
因此可以正向遍历数组 得到数组 的每个元素值,反向遍历数组 得到数组 的每个元素值。
在得到数组 和 的每个元素值之后,对于 ,下标 处能接的雨水量等于 。遍历每个下标位置即可得到能接的雨水总量。
方法二 单调栈
除了计算并存储每个位置两边的最大高度以外,也可以用单调栈计算能接的雨水总量。维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 中的元素递减。从左到右遍历数组,遍历到下标 时,如果栈内至少有两个元素,记栈顶元素为 , 的下面一个元素是 ,则一定有 。如果 ,则得到一个可以接雨水的区域,该区域的宽度是 ,高度是 ,根据宽度和高度即可计算得到该区域能接的雨水量。
具体的代码如下:
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
#define Max(a,b) (a>b)?a:b
#define Min(a,b) (a<b)?a:b
int dp(vector<int>& height,int n)
{
vector<int> leftMax(n,0);
leftMax[0]=height[0];
for(int i=1;i<n;i++)
{
leftMax[i]=Max(leftMax[i-1],height[i]);
}
vector<int> rightMax(n,0);
rightMax[n-1]=height[n-1];
for(int i=n-2;i>=0;i--)
{
rightMax[i]=Max(leftMax[i+1],height[i]);
}
int sum=0;
for(int i=0;i<n;i++)
{
int tmp=Min(leftMax[i],rightMax[i]);
sum+=(height[i]-tmp);
}
return sum;
}
int mono(vector<int>& height,int n)
{
stack<int> S;
S.push(0);
int sum=0;
for(int i=1;i<n;i++)
{
if(height[i]>height[S.top()])
{
while(height[S.top()]<height[i])
{
int cur=S.top();
S.pop();
if(S.empty())
{
break;
}
sum+=(Min(height[S.top()],height[i])-height[cur])*(i-S.top()-1);
}
}
S.push(i);
}
return sum;
}
int main() {
int n;
cin>>n;
vector<int> height(n);
for(int i=0;i<n;i++)
{
cin>>height[i];
}
cout<<dp(height,n)<<endl;
cout<<mono(height,n)<<endl;
return 0;
}