当青训营遇上码上掘金
青训营期间来一波码上掘金,那么势必要来一场激情的碰撞,今天我选择的主题是攒青豆,青豆给我攒的满满的。
主题 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 个单位的青豆。
看到上面的题目突然想到了之前刷过的一道题目:接雨水。立马去回顾了当时做接雨水问题时的解决思路。
对于这个题目我们首先要想到的解决方案就是动态规划。那么首先就需要明白动态规划是什么?
原理
思想:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。按顺序求解子阶段,前面子问题的解为后面子问题的求解提供信息。
如果某一问题有很多重叠子问题,使用动态规划是最有效的。
动态规划中每一个状态一定是由上一个状态推导出来。
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。
适用的情况
- 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,称该问题具有最优子结构,即满足最优化原理。
- 没有后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说:某状态以后的过程不会影响以前的状态,只与当前状态有关。
- 有重叠子问题:子问题之间是不独立的,一个子问题在下一个近阶段可能被多次遇到。(这条性质不是动态规划适用的必要条件,但是具备这条性质那么动态规划相对于其他算法就具备一定的优势)。
做题步骤
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
攒青豆题目详解
回归到该题目中,对于处,接到的青豆数量的高度为两边的最大高度的最小值减去此时处于处的本身柱子的高度。首先想到的方法就是从左从右依次记录两边的最大高度,然后计算每个下标能够接的青豆量,最后相加。
我们通过动态规划的方法,可以在的时间里得到攒青豆的数量。
首先我们需要两个数组leftMax和rightMax分别保存左边和右边的最大柱子高度。记得一定要处理边界值:leftMax的最左边为的位置,rightMax的最右边也就是的位置。
其余位置的就可以通过我们的计算进行存储柱子的最高高度。
在得到我们需要的柱子高度后我们能够攒下青豆的数量值等于Math.min(leftMax[i], rightMax[i]) - height[i]。这个代码的含义就是得到位置两边的最大高度减去此处柱子的高度。依次循环相加就可以得到我们能够攒下的青豆量。
代码
/**
* 支持 import Java 标准库 (JDK 1.8)
*/
import java.util.*;
/**
* 解决攒青豆:使用动态规划
*/
public class Main {
public static void main(String []args) {
// Enter n values: Represents the height of each column
int[] height = {5,0,2,1,4,0,1,0,3};
// Save the final result
int result = 0;
result = sum(height);
System.out.println(result);
}
public static int sum(int[] height) {
// length is the record
int len = height.length;
// Boundary treatment
if (len == 0) {
return 0;
}
// save i left threshold
int[] leftMax = new int[len];
leftMax[0] = height[0];
for (int i = 1; i < len; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
// save i right threshold
int[] rightMax = new int[len];
rightMax[len - 1] = height[len - 1];
for (int i = len - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
// The green bean quantity is equal to the minimum value on the left and right sides - the current height
int num = 0;
for (int i = 0; i < len; ++i) {
num += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return num;
}
}