当青训营遇上码上掘金
题目
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
上图中的例子1:
输入:height=[5,0,2,1,4,0,1,0,3]
输出:17
解释:
5~4是一个区间段(中间的都比它们小),4~3也是一个区间段。
4<5,4~5中间有三个柱子,4*3-(0+2+1)=9;同理可得下一个区间段可容纳的青豆数为8,9+8=17。
自己给一个例子2:
输入:height = [0,0,2,1,4,0,1,0,3]
输出:9
解释:(2*1-1)+(3*3-0-1-0)=9
题解
从题意上看,这题更希望我们用区间来解答,不过笔者太菜了就用的暴力解法(只要不是最坏情况复杂化度就不会到n方的)。
题解大概步骤
1.我们用height数组来存放柱子高度,用一个和height同样长度的数组来标记height数组的一些节点(初始化为0)。
int n;//柱子数量
int height[10000];//定义数组
int flag[100000];//这个数组用来标记
cin>>n;//输入n;
//输入数组顺便初始化标记数组
for(int i=0;i<n;i++){
flag[i]=0;
cin>>height[i];
}
2.寻找区间的端点(两端点之间的单个值一定比端点值小)并存放在flag数组里面,很明显例子1的端点找出来[5,4,3],找完之后得到的flag一定是[5,0,0,0,4,0,0,0,3]。
3.找到端点之后对原数组进行分区间计算,flag[i]==0的i对应的height里的i一定不是端点。以上面得到的flag=[5,0,0,0,4,0,0,0,3]为例,flag[0]==5>flag[4]=4,flag[0-4]有3个值为0(这里是用来装青豆的,可以参考样例图中的位置),所以4*3=12是这个区间理想的容量,但是还要减去中间三个组织的高度(height[1-3],对应上图分别是0,2,1),所以该区间实际容量为12-0-2-1=9;同理下一个区间的容量为8。
寻找区间端点
1.用一个start来代表起始端点的高度,循环之前初始化默认起始端点为0并标记到flag里面,对应的起始段端点高度就为height[0],例子1里面的起始端点高度就是5了;
int start=height[0];//初始化起点高度start
flag[0]=height[0];//初始化标记,默认0位置是起点
2.每次外循环开始都定义一个Max(用来记录当前节点之后的最大的值,有可能如例子1里面5是起始端点高度且是最大的,那么这之后最大的4一定也是一个端点(下一个起点)),一个index记录下一个起始端点的索引,一个f用来标记是否找到比当前起始高度值大的端点。每次外循环定义初始化是保证内循环用到的都是新的。
for(int i=0;i<n;i++){
int Max=-1;//最大值初始化
int index=0;//区间起始索引初始化
int f=0;//用来标记的
}
3.内循环寻找当前节点之后是否有比当前起始高度(初识化的第一个节点就是起始端点)高或相等的,如果有就记录并更新起始端点索引和起始高度然后退出内循环;如果没有找到,就找最大的那个的值和索引用index记录索引,用Max记录那个值,等到内循环完后进行处理(因为只有内循环完了才能保证那个Max一定是最大的那个)。
for(int j=i+1;j<n;j++){
if(height[j]!=0){
//首先我们遍历的这些高度不能为零,因为起始可能为零。
//如果初始化的第一个起始高度为零,那么下一个起始高度就不应该为零。
//比如:[0,0,2,1,4,0,1,0,3]的初识起始高度为0,下一个起始高度应该为2
if(height[j]>=start){
//这里找到了比当前起点高或者相等的,也就是下一个起点。
start=height[j];//刷新一下起点的值
i=j;//更新起始点索引
f=1;//这里进行标记找到了比当前起始高度高或者相等的值
flag[j]=height[j];//这里用flag标记当前索引是一个端点
break;//找到之后跳出循环。
}
}
if(f==0){
//利用标记判断,这里是当前节点之后的所有节点没有比当前节点高的。
//这里在后面所有的节点中找最大的那一个(最大的那一个一定是下一个起点)
//记录那个节点的索引和值。
if(height[j]>Max){
index=j;//记录索引
Max=height[j];//记录值
}
}
}
4.当内循环没有找到比当前起始高度高或相等的端点,外循环对当前节点之后的最大的那个节点和对应的值进行处理,也就是更新当前起始点索引并标记这个索引。
if(f==0){
//这里便是上面循环遍历完之后没找到比当前节点大的,这里进行处理
i=index;//将当前索引移到下一个起点
flag[index]=Max;//记录这个索引
}
计算
- 用例子1里面height1=[5,0,2,1,4,0,1,0,3]找到的区间端点得到的flag一定会为flag1=[5,0,0,0,4,0,0,0,3]。
- 用例子2里面的height=[0,0,2,1,4,0,1,0,3]经过上面流程得到的flag2=[0,0,2,0,4,0,0,0,3]
1.用sum表示总和(最后输出的那个答案),sum1表示每个区段需要减去的和(也就是每个区段flag[i]==0对应的height[i]的和),pos表示每个区段flag[i]==0出现的次数,statrt1表示一个起始端点高度用来和下一个端点高度做比较(初始值为height[0])。
int sum=0;
int sum1=0;
int pos=0;
int start1=height[0];
2.依次遍历flag,如果flag[i]==0,就pos++计数一次,sum1+=height[i];如果flag[1]!=0,即遍历到了一个起始端点,但分两种情况,情况1 height[0]!=0和情况2 height[0]==0,如果是情况2如例子2,那么我们计算的pos和sum1就要先清零再计算,两情况一个是先计算再清零一个是先清零再计算,唯一不同的是情况2而使用完之后要把height[0]标记为一个不为零的数。
for(int i=0;i<n;i++){
if(flag[i]==0){
//代表当前不是端点
sum1+=height[i];//求该区段要减去的和,如例子1里面5-4之间0,2,1的和
pos++;//两个端点之间的节点数,如例子1里面的5-4之间有3个节点
}else{
if(height[0]!=0){
//情况1
int Min=min(start1,flag[i]);//这里进行比对,例子1的话start1就是5了,但我们需要的是小的那个4
sum+=(Min*pos-sum1);//这里是求每个区段和的总和,
start1=flag[i];//这里就把start1更新一下,例子1的话下一次比较就是4和3比较了
//这两个计数值用完了得重新清零。
pos=0;
sum1=0;
}else{
//情况2,如例2,
pos=0;
sum1=0;
int Min=min(start1,flag[i]);
sum+=(Min*pos-sum1);
start1=flag[i];
height[0]=1;
}
}
}
完整代码
int n;//柱子数量
int height[10000];//定义数组
int flag[100000];//这个数组用来标记
cin>>n;//输入n;
//输入数组顺便初始化标记数组
for(int i=0;i<n;i++){
flag[i]=0;
cin>>height[i];
}
int start=height[0];//初始化起点高度start
flag[0]=height[0];//初始化标记,默认0位置是起点
for(int i=0;i<n;i++){
int Max=-1;//最大值初始化
int index=0;//区间起始索引初始化
int f=0;//用来标记的
for(int j=i+1;j<n;j++){
if(height[j]!=0){
//首先我们遍历的这些高度不能为零,因为起始可能为零。
//如果初始化的第一个起始高度为零,那么下一个起始高度就不应该为零。
//比如:[0,0,2,1,4,0,1,0,3]的初识起始高度为0,下一个起始高度应该为2
if(height[j]>=start){
//这里找到了比当前起点高或者相等的,也就是下一个起点。
start=height[j];//刷新一下起点的值
i=j;//更新起始点索引
f=1;//这里进行标记找到了比当前起始高度高或者相等的值
flag[j]=height[j];//这里用flag标记当前索引是一个端点
break;//找到之后跳出循环。
}
}
if(f==0){
//利用标记判断,这里是当前节点之后的所有节点没有比当前节点高的。
//这里在后面所有的节点中找最大的那一个(最大的那一个一定是下一个起点)
//记录那个节点的索引和值。
if(height[j]>Max){
index=j;//记录索引
Max=height[j];//记录值
}
}
}
if(f==0){
//这里便是上面循环遍历完之后没找到比当前节点大的,这里进行处理
i=index;//将当前索引移到下一个起点
flag[index]=Max;//记录这个索引
}
}
int sum=0;
int sum1=0;
int pos=0;
int start1=height[0];
for(int i=0;i<n;i++){
if(flag[i]==0){
//代表当前不是端点
sum1+=height[i];//求该区段要减去的和,如例子1里面5-4之间0,2,1的和
pos++;//两个端点之间的节点数,如例子1里面的5-4之间有3个节点
}else{
if(height[0]!=0){
//情况1
int Min=min(start1,flag[i]);//这里进行比对,例子1的话start1就是5了,但我们需要的是小的那个4
sum+=(Min*pos-sum1);//这里是求每个区段和的总和,
start1=flag[i];//这里就把start1更新一下,例子1的话下一次比较就是4和3比较了
//这两个计数值用完了得重新清零。
pos=0;
sum1=0;
}else{
//情况2,如例2,
pos=0;
sum1=0;
int Min=min(start1,flag[i]);
sum+=(Min*pos-sum1);
start1=flag[i];
height[0]=1;
}
}
}
cout<<sum<<endl;
小结
笔者功底有限没能想出更好的解法,希望能够帮助到大家,如果有伙伴有更好的方法也可以分享给我。