当青训营遇上码上掘金之攒青豆
当青训营遇上码上掘金
题目分析
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
要想知道能接住多少豆子,我们可以按照列的形式先进行计算。即我们计算每一列可以接多少豆子,之后再将它们相加即可。
怎么判断每一列是否可以接豆子呢?从图中可以看出:当前列能不能接豆子,取决于左右两边是否都有比当前列高的柱子,如果有的话,那么就一定可以接到豆子。那如何计算可以接多少呢?这就取决于左右最高柱子的短板了,计算公式为:(左边最高柱子高度,右边最高柱子高度)取更小的那一个,减去当前柱子的高度即可。
有了思路,我们下面直接上代码
public static int saveBeans1(int[] height){
int len=height.length;
int[] left=new int[len];
int[] right=new int[len];
//计算left数组
for(int i=1;i<len;i++){
left[i]=Math.max(left[i-1],height[i-1]);
}
//计算right数组
for(int i=len-2;i>=0;i--){
right[i]=Math.max(right[i+1],height[i+1]);
}
int ans=0;
for(int i=1;i<len-1;i++){
int minHeight=Math.min(left[i],right[i]);
if(minHeight>height[i]) ans+=minHeight-height[i];
}
return ans;
}
left[i]和right[i]分别代表下标i之前最高柱子的高度和下标i之后最高柱子的高度。minHeight就代表我们之前提到的(左边最高柱子高度,右边最高柱子高度)中的短板。
由于我们使用了left数组和right数组,所以空间复杂度为O(N)
空间优化
回看这道题目,仔细思考一下:我们真的需要left数组和right数组吗?
在计算每一列的时候,我们只需要它左边柱子的最大高度和右边柱子的最大高度,这里我们先用leftMax和rightMax指代。如果我们还是依照从左往右的遍历顺序进行遍历,那么leftMax可以正常计算,而rightMax还是需要嵌套一层循环进行计算,这样时间复杂度就上升到平方级别了。因此我们不再使用一个指针,而是借助双指针,从两边向中间收缩,那么leftMax和rightMax都可以正常计算。
那么这里计算的leftMax和rightMax具体的意义是什么呢?我们先用left和right表示两个指针,leftMax即left下标左边(包括当前位置)的柱子最大高度,rightMax即right下标右边(包括当前位置)的柱子最大高度。
这里有几个隐含条件:1.左指针的leftMax<=右指针的leftMax 2.左指针的rightMax>=右指针的rightMax
因此,当左指针的leftMax<右指针的rightMax时,由条件2即可推出左指针的左指针的leftMax<左指针的rightMax,这样就可以计算左指针位置的可接青豆数;同理,当条件相反时,可计算右指针位置的可接青豆数。
思路有了,代码如下:
public static int saveBeans2(int[] height){
int len=height.length;
int left,right;
left=0;
right=len-1;
int leftMax=0,rightMax=0;
int ans=0;
while(left<right){
leftMax=Math.max(height[left],leftMax); //左指针的leftMax
rightMax=Math.max(height[right],rightMax); //右指针的rightMax
if(leftMax<rightMax){
//左指针的leftMax<右指针的rightMax -> 左指针的leftMax<左指针的rightMax
//左指针所属位置青豆计算
ans+=leftMax-height[left];
left++;
} else{
ans+=rightMax-height[right];
right--;
}
}
return ans;
}
总结
其实这道题目就是力扣上的接雨水,只是换了个说法而已。对于大多数读者,估计第一想法就是解法一(数组);空间优化这种解法确实不好理解,如果还是不懂,可以自己在草稿纸上把几个规则列出来,配合着画图,还是能够啃下来的。