当青训营遇上码上掘金-主题4-接青豆
当青训营遇上码上掘金
题目描述
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
输入样例
输入: height = [5,0,2,1,4,0,1,0,3]
输出:
17
解析:
上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆
题目解析
题目来源
Leetcode上的接雨水:42. 接雨水 - 力扣(LeetCode)
思路求解
-
任意两个不相等高度的立柱围成的范围(如上如图的l和r两根柱子),如果之间的柱子高度(h1到h6)均小于两侧柱子的高度。 那么这个范围内接的青豆(雨水)为最高的两个柱子(l和r)中矮的那根(r)减去范围内所有柱子的高度之和, 即上图红色阴影部分
-
我们假设一个定义:由最高和次高柱子作为最两侧边界框出的范围为标准范围,如上图就是一个标准范围其由最高
l
和次高r
框出。 -
上面描述了找到收集青豆的思想,一个标准范围内的青豆数量就是次高立柱在区间内的高度差的和。
-
题目的整体思路也就出来了。所有柱子的排列都可以划分成一块块标准范围的区间,而我们要做的就是求出来所有标准范围的总和。
-
我们关心算法的边界条件:最高的那根柱子,因为按照我们的划分,最高的柱子一定是某个标准范围的边界,而原先排列左右两侧的柱子也是标准范围的边界。
复杂度分析
由于所有标准范围都是连续的(也就是说一个标准范围与另一个标准范围一定是紧密相连的),而求解落在标准范围内的青豆也是线性遍历,所以整个算法的复杂度为O(n)
代码实现
public class Main {
public static void main(String []args) {
System.out.println(trap(new int[]{5,0,2,1,4,0,1,0,3}));
}
public static int trap(int[] height) {
// i为初始化为最左侧柱子下标,同理j为最右侧下标
int i = 0, ir = i, j = height.length - 1, jr = j, sum = 0;
// ir和jr分别是最左和最右两个标准范围的边界探测下标指针
ir = i + 1; jr = j - 1;
// 当两边的探测指针相撞说明遍历完了整个柱子数组,即可结束循环
for (int count = 0; count < height.length && ir <= jr; count++) {
// 我们优先考虑从边界柱子最矮的标准范围开始遍历,这样最终总数就只用正向求和即可
if (height[i] <= height[j]) {
// 当游标未遍历到数组尾端或者找到了比左侧边界要矮的柱子就当作区间柱子,加上与左边界的差值
if (ir < height.length && height[ir] < height[i]) {
sum += (height[i] - height[ir]);
} else {
// 否则说明当前以及遍历完了一个标准区间,将i设为下一个标准区间的左端点
i = ir;
}
// 探索游标每次向右移动
ir++;
} else {
// 右侧探索同理
if (jr >= 0 && height[jr] < height[j]) {
sum += (height[j] - height[jr]);
} else {
j = jr;
}
jr--;
}
}
return sum;
}
}