当青训营遇上码上掘金。青训营主题创作活动,主题4:攒青豆。
一、问题描述
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。
图1
二、问题分析
(1)首先两根柱子之间可以存放的青豆数取决于最小的柱子高度,其次在中间的柱子又会受到旁边柱子高度的影响,如果旁边柱子的高度比该柱子高,则所存储的青豆数取决于旁边的柱子,而不是该柱子。
(2)可能存在的情况:a、最高的柱子和次高的柱子分别在数组的两端,或者两端的倒数第二的位置,那么只需要计算中间柱子的高度和次高柱子的高度之差就是每根柱子上可容纳的青豆数了。b、最高柱子在最左最右或者次左次右,此时需要向一侧分区域计算青豆数。c、最高柱子在数组的中间位置,此时需要向两侧分别分区间计算青豆数。具体分类如图2所示,图3对位置做了定义。(备注:在情况a和b中,当最高柱子或次高柱子在正数或倒数第二的位置时,那它旁边的柱子则不存在青豆)
图2
图3
三、算法介绍
(1)用maxvalueIndex(int[] height, int left, int right)函数确定最大值的位置 (2)根据最大值分为三种情况,最大值在最右侧,最大值在最左侧,最大值在中间 (3)最大值在最左侧时,调用函数fun1(int[] height, int left, int right)计算青豆数;最大值在最右侧时,调用函数fun2(int[] height, int left, int right)计算青豆数;最大值在中间时,先将数组根据最大值位置分成两个区间,再分别调用fun1和fun2函数。 (4)其中用到了递归的方法,将数组不停地分区间,直到分成最小的区间(图2中的情况a)即可计算青豆数。
四、相关代码
(1)算法类
public class QingDou {
private int count = 0;
/**
* 分三种情况,先判断最大值在什么位置,再确定用哪一种方法
*
* @param height
* @return
*/
public int sum(int[] height) {
int len = height.length;
int index = maxvalueIndex(height, 0, len - 1);
if (index == 0) {// 最大值在最左边
fun1(height, 0, len - 1);
} else if (index == len - 1) {// 最大值在最右边
fun2(height, 0, len - 1);
} else {// 最大值在最中间,则拆分成两个数组分别计算
//计算最大值左侧可容纳的青豆数
fun2(height, 0, index);
//计算最大值右侧可容纳的青豆数
fun1(height, index, len - 1);
}
return count;
}
/**
* 最大值在最左边,计算青豆数
*
* @param height
* @param left
* @param right
*/
public void fun1(int[] height, int left, int right) {
//找出次大值点的位置
int index = maxvalueIndex(height, left + 1, right);
//如果次大值点的位置不在right的位置或right-1的位置,则继续拆分,计算次大值点到right中间可容纳的青豆数
if (index != right && index != right - 1) {
fun1(height, index, right);
}
//计算最大值点和次大值点之间可容纳的青豆数
for (int i = left + 1; i < index; i++) {
count += height[index] - height[i];
}
}
/**
* 最大值在最右边,计算青豆数
*
* @param height
* @param left
* @param right
*/
public void fun2(int[] height, int left, int right) {
//找出次大值点的位置
int index = maxvalueIndex(height, left, right - 1);
//如果次大值点的位置不在left的位置或left-1的位置,则继续拆分,计算次大值点到left中间可容纳的青豆数
if (index != left && index != left + 1) {
fun2(height, left, index);
}
//计算最大值点和次大值点之间可容纳的青豆数
for (int i = right - 1; i > index; i--) {
count += height[index] - height[i];
}
}
// 判断最大值的位置
public int maxvalueIndex(int[] height, int left, int right) {
int index = left;
for (int i = left + 1; i <= right; i++) {
if (height[index] < height[i]) {
index = i;
}
}
return index;
}
}
(2)测试结果
public class test {
public static void main(String[] args) {
QingDou util = new QingDou();
int[] height1 = {5,0,2,1,4,0,1,0,3};//结果为17
int[] height2 = {3,0,1,0,4,1,2,0,5};//结果为17
int[] height3 = {3,0,1,0,5,1,2,0,4};//结果为17
int[] height4 = {1,2,3,4,5,6,7,8,9};//结果为0
int[] height5 = {4,0,5,3,4,3,2,6,5};//结果为12
int sum = util.sum(height5);
System.out.println(sum);
}
}
五、个人总结
看到问题时首先要考虑到所有的情况,需要考虑算法在特殊情况下的适应性。为了使代码看着清晰易读,最好把一些业务封装成方法。编程之前最好先把用到的函数先定义好,可以加快开发效率。 算法中用到了递归的方法,可以使代码结构变得简单,可读性强。在一些递归次数不是很多的情况下,递归的方法比循环要好用一些。