当青训营遇上码上掘金
主题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 个单位的青豆。
问题理解
通过观察发现,这题关键是找到不同区间的支柱,每个区间由2个柱子组成分别为柱子n以及柱子n向左或向右找到第一个的比柱子n高的柱子,如果没有就是仅比柱子n低的柱子。然后用长*宽-两个支柱之间的所有柱子高度,便可算出该区间可以接收的青豆数,总青豆数=所有区间可以接收的青豆数之和
题目描述中支柱从左到右分析分别为第1个黑柱子(5),第4个黑柱子(4),第6个黑柱子(3),总计3个支柱组成的2个区间
所以总青豆数==17
暴力法
最简单的暴力法,思想为柱子1开始从左往右,找第一个比该柱子大的柱子n,如果没有就找仅比该柱子小的柱子n,以这两个柱子之间为区间计算青豆数,之后以柱子n为起点继续往右寻找,循环往复直到所有区间找完,此方法当遇到柱子大小为(n,n-1,n-2,n-3.....1)这种逐级递减时,时间复杂度最大为O(n^2)
优化方案
如果每个柱子都知道其左右两边的最大高度,那么计算时就可以降低时间复杂度,因此我们尝试先遍历数组计算出每个柱子左右的最大高度,然后利用生成的两个数组,来计算出每个位置能存多少青豆。此时时间复杂度为O(n)
class Main {
public static int compute(int[] heights) {
int n = heights.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];//记录每个柱子左边的最大高度
leftMax[0] = heights[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], heights[i]);
}
int[] rightMax = new int[n];//记录每个柱子右边的最大高度
rightMax[n - 1] = heights[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], heights[i]);
}
int nums = 0;//青豆数
for (int i = 0; i < n; ++i) {
nums+= Math.min(leftMax[i], rightMax[i]) - heights[i];
}
return nums;
}
public static void main(String[] args) {
int[] heights=new int[]{5,0,2,1,4,0,1,0,3};
System.out.println(compute(heights));
}
}