题目
分析题目
题目让我们求此排列柱子总共能接多少水,总共接的水等于每个柱子能接的水之和,因此只要求出每个柱子能接的水量就行了
一个柱子能接的水量由他两旁高于他的柱子决定,而两侧最高的柱子中较小的柱子决定了柱子能接的最大水量
求第i个柱子能接多少水就转化为求i项的前缀最大值和后缀最大值之间的较小值问题
而第i个柱子的接水量等于较小值减去这个柱子的高度
其中值得注意的是第i个柱子的前缀最大值或后缀最大值的较小值如果比第i个柱子还矮或者一样高的话,那么第i个柱子是不能接住水的(对一个柱子而言,只有两边都高于自己才可以接住水,也就是说较小值最小也就是第i个柱子本身),因此在后续写代码求最大值时,应包含第i个柱子,(第i个柱子的高度减去自己的高度恰好为0),所以我们比较的是第i柱子前面和后面的柱子,而求最大值时还得包含第i个柱子,我说有点拗口,这点结合代码会更理解,别担心,后面就会懂了,这里依旧是运用动态规划的方法做题
运用动态规划
设计状态:求第i项前面的最大值(包含第i项)就转移到求【第i-1项之前的最大值(包含第i-1项)与第i项之间】的最大值,求后缀最大值也如出一辙
写出状态方程:premax[i]=max(premax[i-1],a[i]); postmax=max(postmax[i+1],a[i]);
设定初始状态:前缀最大值中 premax[0]=a[0]; 后缀最大值中 postmax[n-1]=a[n-1]
执行状态转移
返回最终解
代码段
int max(int a,int b) //定义一个求最大值的函数
{
return a>b?a:b;
}
int min(int a,int b) //定义一个求最小值的函数
{
return a<b?a:b;
}
void PreMax(int* a,int n,int *premax)
{
int i;
for(i=0;i<n;i++)
{
if(i==0)
{
premax[i]=a[i]; //初始状态
}
else
{
premax[i]=max(premax[i-1],a[i]); //执行状态转移
}
}
}
void PostMax(int*a,int n,int* postmax)
{
int i;
for(i=n-1;i>=0;i--)
{
if(i==n-1)
{
postmax[i]=a[i]; //初始状态
}
else
{
postmax[i]=max(postmax[i+1],a[i]); //执行状态转移
}
}
}
#define MAXN 20005
int trap(int* height, int hightSize){
int premax[MAXN]; //定义一个存放前缀最大值的数组
int postmax[MAXN]; //定义一个存放后缀最大值的数组
int i,sum=0; //sum存放总水量
PreMax(height,hightSize,premax);
PostMax(height,hightSize,postmax);
for(i=0;i<hightSize;i++)
{
sum+=min(premax[i],postmax[i])-height[i]; //计算接水量,这里是值得注意的点
}
return sum;
}
执行结果
时间复杂度和空间复杂度分析
代码共执行3n次,因此时间复杂度为O(n)
定义了两个数组,因此空间复杂度为O(n)
总结
1 - 这道题虽然写着困难的标签,都是只要仔细分析,还是可以找到巧妙之处,即求前后缀最值问题
2 - 还要注意求解接水量的时候计算步骤,知道较小值最小也是第i个柱子本身