动态规划法解决攒青豆问题

122 阅读2分钟

当青训营遇上码上掘金

题目描述

主题 4:攒青豆

现有 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 个单位的青豆。

动态规划法

首先我们可以用一个式子表示我们最终要求得的解,即某一位置左右两边柱子高度的较小值,减去此位置本身的高度,即是解的表达,表达式可写成:
result = Math.min(max_left[i],max_right[i]) - height[i]
接下来我们要通过遍历每个位置的情况对result进行累加,首先必须记录每个位置的高度情况,放入两个数组中,一个代表位置左边的高度情况,一个代表位置右边的高度情况,首先第一个和最后一个位置显然是不能放青豆的,所以在开始时直接置0,之后循环遍历max_left数组与max_right数组,并为两数组中每一位置情况赋值,表达式如下:
max_left[i] = Math.max(max_left[i-1],height[i-1]);
max_right[i] = Math.max(max_right[i+1],height[i+1]);

完整代码如下:

import java.util.*;

// 动态规划法
public class Main {
   public static void main(String []args) {
      // Scanner inputHeight = new Scanner(System.in);

      int[] height = {5,0,2,1,4,0,1,0,3};
      System.out.println(count(height));
   }

   public static int count(int[] height) {
        int n = height.length;
        int[] max_left = new int[n];
        int[] max_right = new int[n];
        int ans = 0;
        // 第一个和最后一个位置不能放青豆
        max_left[0] = 0;
        max_right[n-1] = 0;

        if(n < 2) return 0;

        for(int i = 1;i < height.length;i++){
            // 一个位置左边所能提供的最大高度
            max_left[i] = Math.max(max_left[i-1],height[i-1]);

        }

        for(int i = n-2;i >= 0 ;i--){
            // 一个位置右边所能提供的最大高度
             max_right[i] = Math.max(max_right[i+1],height[i+1]);
        }

        for(int i = 1;i < n-1;i++){
            // 判断避免为负
            if(Math.min(max_left[i],max_right[i]) > height[i]){
              // 累加
              ans += Math.min(max_left[i],max_right[i]) - height[i];
            }
        }

        // 返回青豆数
        return ans;
    }


}

复杂度分析

时间复杂度:O(n),其中 n 是数组 height 的长度。计算数组 max_left 和 max_right 的元素值各需要遍历数组 height 一次,计算青豆总量还需要遍历一次。
空间复杂度:O(n),其中 n 是数组 height 的长度。需要创建两个长度为 n 的数组 leftMax 和 rightMax

其他注意

首部和尾部不可能有青豆所以循环的时候不需要,注意边界条件,然后遍历相加的时候注意判断是否大于其本身高度,否则会出现ans为负的情况