当青训营遇上码上掘金
题目:攒青豆
现有n个宽度为1的柱子,给出n个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析: 输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
解法
1.按列求解,dp
简单的思路就是,计算出每一列储豆子的高度:无外乎就是:当两边最大高度都大于当前高度时可以储存豆子,储存豆子的单位数目是两边最大高度中较低的高度和当前高度的差值。此方法消耗比较大的是要维护一个左右最大值的数组。此处可以利用dp的思路进行。
tips:头和尾是不会储存豆子的
如图所示,逐列进行求和:
函数代码列在下方:
public static int count(int[] height) {
int tail = height.length;
if (tail <= 2) return 0;
int[] leftmax = new int[tail];
int[] rightmax = new int[tail];
leftmax[0] = height[0];
rightmax[tail - 1] = height[tail - 1];
int res = 0;
//利用dp的思路,维护左右最大的数组
for (int i = 1; i < tail - 1; i++) {
if (height[i - 1] > leftmax[i - 1]) leftmax[i] = height[i - 1];
else leftmax[i] = leftmax[i - 1];
if (height[tail - i] > rightmax[tail - i]) rightmax[tail - i - 1] = height[tail - i];
else rightmax[tail - i - 1] = rightmax[tail - i];
}
//按照每一列,逐列加和
for(int i=1;i<tail-1;i++){
if(height[i]<leftmax[i]&&height[i]<rightmax[i]){
int temp=leftmax[i]<rightmax[i]?leftmax[i] :rightmax[i];
res+=(temp-height[i]);
}
}
return res;
}
2.双指针求解
由于使用dp的问题,开辟了诸多空间。我们可以看到,实际上能接多少豆子,事实上和两边中较小的有关系,也是先从小的一边开始接。就如同大水漫灌,低的地方总是先灌进来,也先灌满。
据此,想到了双指针的办法,从两边向中间走,不断维护容器的当前基准,靠近当前基准一侧的空间能接多少豆子,依赖于当前基准的高度,直到当前基准产生变化。
这样的算法和刚刚是一样的,但是节省了空间和时间。
如图:
很显然,由于3比5要小,在右指针到达4之前,已经接到的豆子全是依赖于3这个高度的。
函数代码列在下方:
public static int count1(int[] height) {
int left = 0, right = height.length - 1;
int res = 0, max = 0;// 标记基准高度
//双指针碰撞则结束
while (left < right) {
//只要基准没变,就一直按照原基准接豆子
while (left < right && height[left] <= max) {
res += (max - height[left]);
left++;
}
while (left < right && height[right] <= max) {
res += (max - height[right]);
right--;
}
//哪边小,哪边就是基准
max = height[left]<height[right]?height[left]:height[right];
}
return res;
}
结果展示:
用例:
public static void main(String []args) {
int[] height={5,0,2,1,4,0,1,0,3};
int x =count(height);
int y =count1(height);
System.out.println("青豆总数为:");
System.out.println(x);
System.out.println(y);
}
结果:
代码链接为: