当青训营遇上码上掘金
攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
解法一 暴力解法
思路
通过观察规律,可以知道,每个柱子所能接到的青豆数取决于两个重要的因素,第一个是该柱子左侧高度最高的柱子,另外一个是该柱子右侧高度最高的柱子。两柱子之间高度较小的即为该柱子在接完青豆后所能达到的最大值。 因此按照该思路,我们可以写出以下代码
代码
public int getBeans(int[] arr){
int res = 0;
for (int i=0;i<arr.length;i++){
int leftMax = 0;
int rightMax = 0;
for (int j = i; j >= 0; j--) {
leftMax = Math.max(leftMax, arr[j]);
}
for (int j = i; j < arr.length; j++) {
rightMax = Math.max(rightMax, arr[j]);
}
res+=Math.min(leftMax,rightMax)-arr[i];
}
return res;
}
算法分析
该代码首先遍历该数组,对于数组中的每一个元素,分别利用两个循环来寻找其左侧和右侧的最高柱子高度,记为leftMax和rightMax,然后取两者之中的最小值。按照之前的思路,
,因此可以得到
,所以该柱子所能接到的青豆数如上式所示,然后将每个柱子所能接到的青豆数加到res中,就可以得到所能接到的所有青豆数。
以第3列为例,该柱子高度为2,其左侧柱子最高为5,右侧柱子最高为4,因此其所能接到的青豆数为
,图解如下图所示
复杂度分析
对于时间复杂度来说,采用两层循环,每一层循环都遍历了整个数组,因此时间复杂度为
对于空间复杂度来说,只用到了两个临时变量,因此空间复杂度为
解法二 双指针解法
思路
由解法一可注意到,在遍历过程中有很多不必要的计算。我们在求解过程中,每次都需要找到的是该柱子左右两侧最高柱子中的最小柱子,而在从前往后遍历的过程中,我们可以很容易的知道左侧的最高柱子的高度(只需要一个变量来记录),因此,此时需要与右侧最高柱子的高度进行比较。
此时想一想,真的要和右侧最高柱子高度比较吗?
如果右侧有一个比左侧最高柱子更高的(但不一定是最高的),那么还有必要继续搜索下去吗?答案很明显,是不用的,原因是我们只需要两者之间更小的,而此时必然左侧的更小,因此直接可以得到该柱子所能接到的青豆数。那么再想想,右侧柱子的遍历只能从该柱子到结尾吗?能不能从后往前遍历呢?
答案也很明显,是可以的。那么此时可以发现,左右两侧是不是几乎完全等效的?一个从前往后,一个从后往前,区别只有前后之差。之前提到,只需要找到比左侧柱子更高的右侧柱子,就可以确定当前柱子的青豆数,那么假设我们从后往前遍历,是不是只需要找到一个比右侧最高(从后往前遍历,同样可以使用一个遍历记录最高柱子)更高的左侧柱子(无需最高)?那么答案就出来了。我们只需要使用两个指针,一个从前往后遍历(即为左指针),一个从后往前遍历(即为右指针),这样,从前往后遍历记录了左指针指向的柱子的左侧最高柱子的值,从后往前遍历记录了右指针指向的柱子的右侧最高柱子的值。对于两指针来说,右指针右侧的柱子都是左指针右侧的柱子,因此只要左指针左侧最高柱子比右指针右侧最高柱子更低,就可以直接得到左指针指向柱子所能接到的青豆,右指针同理。左右指针交叉遍历,在相遇之前就可得到结果。代码
public int getBeans(int[] arr){
int res = 0;
int left = 0, right = arr.length - 1;
int leftMax = 0, rightMax = 0;
while (left <= right) {
leftMax = Math.max(leftMax, arr[left]);
rightMax = Math.max(rightMax, arr[right]);
if (leftMax > rightMax) {
res += rightMax - arr[right];
right--;
} else {
res += leftMax - arr[left];
left++;
}
}
return res;
}
算法分析
该代码使用双指针分别从数组的头部和尾部分别遍历,在遍历过程中,leftMax记录左指针左侧最高柱子,rightMax记录右指针右侧最高柱子。当左指针左侧最高柱子更高时,说明右指针指向的柱子的右侧最高柱子更低,也就能得到右指针所指向柱子所能接到的青豆数,左指针同理。
复杂度分析
对于时间复杂度来说,左右指针在相遇之后就停止了遍历,因此时间复杂度为
对于空间复杂度来说,只有到了几个指针变量和临时变量,因此空间复杂度为
代码片段
查看代码请点击下方Script
总结
本文记录了对攒青豆问题的解决方法,主要提出了两种思路,暴力解法虽然低效,但是易于理解;双指针方法高效,但要想到需要费点功夫。
文章到这就结束了,如有错误,还请指正~