当青训营遇上码上掘金
引言
很荣幸能加入第五届字节跳动青训营,参加 「青训营 X 码上掘金」主题创作活动,非常激动,因为可以攒青豆!
一、问题提出
现有 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.1 动态规划
可以使用动态规划算法解决攒青豆问题,动态规划算法是一种用于求解最优化问题的数学方法,其主要思想是基于重叠子问题的最优化思想,通过将大问题分解为小问题来解决复杂性问题。动态规划算法求解的过程是:定义我们要求解的问题、建立一个表格来存储子问题的结果,然后从最小问题开始,向上解决直到最终问题。动态规划解决攒青豆问题Java核心代码如下:
public static int maxBean(int[] heights) {
int result=0; //result用来存储最终的结果;
int leftmax=0; //leftmax和rightmax分别用来存储左右的最大值;
int rightmax=0;
int left=0; //left和right分别指向数组的头和尾
int right=heights.length-1;
while(left<right) { //当遇到左边的数比右边的大时,如果左边的数大于leftmax,则更新leftmax;
if(heights[left]<heights[right]) { //如果左边的数小于leftmax,则把leftmax-heights[left]加入result中;
if(heights[left]>=leftmax) {
leftmax=heights[left];
} else {
result+=leftmax-heights[left];
}
left++;
} else { //反之,如果右边的数比左边的大,同样的操作。然后最后返回result即可。
if(heights[right]>=rightmax) {
rightmax=heights[right];
} else {
result+=rightmax-heights[right];
}
right--;
}
}
return result;
}
}
这段代码的主要功能是实现攒青豆问题,用到了动态规划算法。主要有三步:初始化(预处理);迭代:通过判断哪一边的数字大于另一边,然后只更新当前边的最大值,如果当前边的数字小于最大值,则表明有青豆被接住,将其加入最终结果中;最后返回最终结果。
2.2 双指针法
使用两个指针,分别指向数组的头部和尾部,然后遍历数组,比较两个指针所指向的柱子的高度。如果左指针所指向的柱子高度小于右指针所指向的柱子,则将左指针所指向的柱子与当前最大高度之差加入到结果中,然后移动左指针;反之,如果左指针所指向的柱子高度大于右指针所指向的柱子,则将右指针所指向的柱子与当前最大高度之差加入到结果中,然后移动右指针。
换一种说法:算法开始从两端向中间靠近,在两端处求出最大的面积,然后依次向中间收敛,在每一步的收敛过程中,求出最大的面积并与之前求出的最大面积进行比较,取最大者。具体操作流程是:从两边同时开始靠近,将最高的那一端作为起点,因为它决定了当前水槽的面积;接着,从两侧分别向中心靠近,如果左侧的高度小于右侧的高度,那么就从左侧向右侧收敛,直到收敛到其中一端为止。在每次收敛过程中,都可以计算出当前水槽的面积,并与之前求出的最大面积进行比较,取最大者。参加了青训营,学习了Go语言,则使用Go语言完成该段代码,Go核心代码如下
/*
攒青豆算法,主要思想是:使用双指针法,从数组的头尾开始,根据两边柱子的高度,不断更新max值,并计算出可以接住的青豆。
*/
func maxBean(height []int) int { // 计算数组height中接住的青豆数量
max := 0 // max代表当前最高的柱子
n := len(height) // n代表height数组的长度
left, right := 0, n-1 // left和right分别指向数组头尾
res := 0 // res用来保存结果
for left < right { // 如果left指向的柱子小于right,则判断left处的柱子高度与max之间的差值,如果差值大于0,则将其加入到res中
if height[left] < height[right] {
if height[left] >= max {
max = height[left]
} else { // 否则,将max更新为left处的柱子高度
res += max - height[left]
}
left++
} else { // 反之,right指向的柱子比left指向的要小,重复上述操作即可
if height[right] >= max {
max = height[right]
} else {
res += max - height[right]
}
right--
}
}
return res
}
3、测试
输入:height = [5,0,2,1,4,0,1,0,3]
输出:获得青豆数量 = 17
输入:height = [7,2,0,2,1,4,0,1,0,3,3]
输出:获得青豆数量 = 19
输入:height = [42,12,5,22,5,1,0,5]
输出:获得青豆数量 = 36
结果正确
4、总结
主要有双指针法和动态规划法两种方法可用于解决攒青豆问题(此类问题)。
- 双指针法的思路是:使用两个指针,分别指向数组的头部和尾部,然后遍历数组,比较两个指针所指向的柱子的高度,不断更新最大高度。
- 动态规划法的思路是:将大问题分解为小问题,构建一个表格来存储子问题的结果,然后从子问题开始逐步向上解决直到最终问题。
最后希望能在青训营活动更多知识和更多的青豆!