题目:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
解题思路
柱子呈锯齿状,情况多变,我们先从某些简单的局部切入进行分析:
首先分析样例中的前三个柱子:[5,0,2],显然,他们可以囤积青豆的区域为下图中的红框中的区域:
同样的,我们再来分析第2、第3、第4根柱子[2,1,4],现在,他们可以囤积青豆的区域为下图中的黄框中的区域:
那么把前五根柱子放在一起来看,会发现又多了一个区域可以囤积青豆,就是下图中的橙色区域:
可以知道,这片区域是由[5,2,4]这三根柱子决定的。
即:[5,0,2]决定红色区域,[2,1,4]决定黄色区域,[5,2,4]决定橙色区域。
类似的,对于样例中的右边,也可以得到类似的结论:
[4,0,1]决定1号区域,[1,0,3]决定2号区域,[4,1,3]决定3号区域。
规律总结:
每根柱子,以及左右两边第一根比其高的柱子,这样的三根柱子围成的区域的面积总和,就是可以囤积青豆的总数。
代码实现
遍历
使用遍历的方法寻找左右两边第一根比其高的柱子并记录下来,最后计算区域总和。
func gather1(heights []int)(int){
length := len(heights)
left := make([]int, length, length);
right := make([]int, length, length);
for i, h := range(heights){
left[i] = i;
for j := i-1; j >=0; j--{
if ( heights[j] > h ){
left[i] = j;
break;
}
}
right[i] = i;
for j := i+1; j<length; j++{
if ( heights[j] >= h ){
right[i] = j;
break;
}
}
}
ans := 0
for i, h := range(heights){
height := min( heights[ left[i] ], heights[ right[i] ] ) - h
ans += height*(right[i]-left[i] -1)
}
return ans
}
时间复杂度O(n^2), 空间复杂度O(n)
单调栈
对于寻找左右两边第一个比自己大(或小)的问题,可以使用单调栈来解决,达到更好的时间性能。
func gather2(heights []int)(int){
length := len(heights)
left := make([]int, length, length);
right := make([]int, length, length);
stack := make([]int, length, length);
top := 0
for i, h := range(heights){
for top > 0 && heights[ stack[top-1] ] <= h {
right[ stack[top-1] ] = i;
// 出栈
top --;
}
if top == 0{
left[i] = i;
} else{
left[i] = stack[top-1];
}
// 入栈
stack[top] = i;
top++;
}
for top > 0 {
right[ stack[top-1] ] = stack[top-1]
// 出栈
top--;
}
ans := 0
for i, h := range(heights){
height := min( heights[ left[i] ], heights[ right[i] ] ) - h
ans += height*(right[i]-left[i] -1)
}
return ans
}
时间复杂度O(n), 空间复杂度O(n)