当青训营遇上码上掘金,我选择进行创作的主题是:
主题 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 个单位的青豆。
求解问题的过程中可以发现,在位置i能容纳的豆子数量单位,取决于其左边最高柱子,和右边最高柱子中的较小者,可以类比于木桶效应的原理。
如果针对每个位置,都对左边和右边的所有柱子遍历求最值,时间复杂度太高。在思考算法优化办法上,我想到动态规划。
首先,动态规划问题的一般形式就是求最值。其次,求解动态规划的核心问题是穷举。
labuladong算法中总结到:
- 虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,需要你熟练掌握递归思维,只有列出正确的「状态转移方程」,才能正确地穷举。
- 而且,你需要判断算法问题是否具备「最优子结构」,是否能够通过子问题的最值得到原问题的最值。
- 另外,动态规划问题存在「重叠子问题」,如果暴力穷举的话效率会很低,所以需要你使用「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。
因此,在攒青豆问题中,可以得到状态转移方程
# 记H(i)为柱子i左边最高的柱子高度,则
H(i+1)=max{柱子i的高度,H(i)}
用数组如果存储下H(i)数据,则可节省大量的时间,由此诞生动态规划法,用两个数组存下每个位置左右的最大高度即可:
public int green_beans(int[] beans){
int len = beans.length;
int[] left_max = new int[len];
int[] right_max = new int[len];
for(int i = 1; i < len; i ++){
left_max[i] = Math.max(left_max[i - 1],beans[i - 1]);
right_max[len - i - 1] = Math.max(right_max[len - i],beans[len - i]);
}
int fill = 0;
for (int i = 0; i < len; i++) {
fill += Math.max(0,Math.min(left_max[i],right_max[i]) - beans[i]);
}
return fill;
}