青训营 X 码上掘金--攒青豆

104 阅读4分钟

当青训营遇上码上掘金

青训营期间来一波码上掘金,那么势必要来一场激情的碰撞,今天我选择的主题是攒青豆,青豆给我攒的满满的。

主题 4:攒青豆

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

725ef710a59944d49d0315bece7a1ac1~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0[1].awebp

以下为上图例子的解析: 输入:height = [5,0,2,1,4,0,1,0,3] 输出:17 解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

看到上面的题目突然想到了之前刷过的一道题目:接雨水。立马去回顾了当时做接雨水问题时的解决思路。

对于这个题目我们首先要想到的解决方案就是动态规划。那么首先就需要明白动态规划是什么?

原理

思想:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。按顺序求解子阶段,前面子问题的解为后面子问题的求解提供信息。

如果某一问题有很多重叠子问题,使用动态规划是最有效的。

动态规划中每一个状态一定是由上一个状态推导出来。

动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。

适用的情况

  1. 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,称该问题具有最优子结构,即满足最优化原理。
  2. 没有后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说:某状态以后的过程不会影响以前的状态,只与当前状态有关。
  3. 有重叠子问题:子问题之间是不独立的,一个子问题在下一个近阶段可能被多次遇到。(这条性质不是动态规划适用的必要条件,但是具备这条性质那么动态规划相对于其他算法就具备一定的优势)。

做题步骤

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

攒青豆题目详解

回归到该题目中,对于ii处,接到的青豆数量的高度为两边的最大高度的最小值减去此时处于ii处的本身柱子的高度。首先想到的方法就是从左从右依次记录两边的最大高度,然后计算每个下标能够接的青豆量,最后相加。

我们通过动态规划的方法,可以在O(n)O(n)的时间里得到攒青豆的数量。

首先我们需要两个数组leftMaxrightMax分别保存左边和右边的最大柱子高度。记得一定要处理边界值:leftMax的最左边为i=0i = 0的位置,rightMax的最右边也就是i=n1i = n - 1的位置。

其余位置的就可以通过我们的计算进行存储柱子的最高高度。

在得到我们需要的柱子高度后我们能够攒下青豆的数量值等于Math.min(leftMax[i], rightMax[i]) - height[i]。这个代码的含义就是得到位置ii两边的最大高度减去此处柱子的高度。依次循环相加就可以得到我们能够攒下的青豆量。

代码

/**
* 支持 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;
    }
}