一起攒青豆 | 当青训营遇上码上掘金

132 阅读5分钟

当青训营遇上码上掘金

引言

很荣幸能加入第五届字节跳动青训营,参加 「青训营 X 码上掘金」主题创作活动,非常激动,因为可以攒青豆

一、问题提出

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积) image.png

以下为上图例子的解析:

输入: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、总结

主要有双指针法和动态规划法两种方法可用于解决攒青豆问题(此类问题)。

  • 双指针法的思路是:使用两个指针,分别指向数组的头部和尾部,然后遍历数组,比较两个指针所指向的柱子的高度,不断更新最大高度。
  • 动态规划法的思路是:将大问题分解为小问题,构建一个表格来存储子问题的结果,然后从子问题开始逐步向上解决直到最终问题。

最后希望能在青训营活动更多知识和更多的青豆!