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

66 阅读3分钟

当青训营遇上码上掘金。

题目

主题 4:攒青豆

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

subject.webp

思路

以2号柱子为例,这个位置上的青豆数量等于左侧最高柱子(5号柱子)的高度和右侧最高柱子(4号柱子)的高度的最小值减去2号柱子自身的高度。 subject.png 用公式化表达为:

对于第i个位置: pointNum = min(leftHighest[i], rightHighest[i]) - beansHeights[i]

而对于求对于某点一侧的最高柱子,从左向右,视当前点与之前最大值的关系,不变或者单调增加,可以采用动态规划的方式优化,具体来讲:

leftHighest[i] = max(beansHeights[i], leftHighest[i - 1]);

rightHighest[i] = max(beansHeights[i], rightHighest[i + 1]);

除此之外,还可以采用vector辅助数据存储,设置数据的输入输出结构。

动态规划背景知识

动态规划既是一种优化方法。可以粗略地认为,它是指通过以递归方式将复杂问题分解为更简单的子问题来简化问题。虽然有些决策问题不能以这种方式分解,但跨越几个时间点的决策往往会递归地分解。如果一个问题可以通过将其分解为子问题,然后递归地找到子问题的最优解来最优地解决,那么它就被称为具有最优子结构。

如果子问题可以递归地嵌套在更大的问题中,以便动态方法适用,那么大问题的值和子问题的值之间存在关系。我们一般把这种公式叫做递推公式。

动态规划主要是对纯递归的优化。无论我们在哪里看到递归解决方案重复调用相同的输入,我们都可以使用动态规划对其进行优化。其思想是简单地存储子问题的结果,这样我们就不必在以后需要时重新计算它们。这种简单的优化将时间复杂性从指数降低到多项式。

所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。

有一个比较好的解决动态规划问题的习惯步骤:

  1. 确定dp表和下标的含义

  2. 确定重复公式

  3. 如何初始化dp数组

  4. 确定遍历顺序

  5. dp数组的示例推导(也是Debug的常用办法)

源代码

#!/jcode/lang/cpp https://xitu.github.io/jcode-languages/dist/lang-cpp.json

#include <bits/stdc++.h>

using namespace std;

int countBeans(vector<int> &beansHeights) {
    // 如果只有两个柱子,则收集不到青豆
    if (beansHeights.size() <= 2)
        return 0;
    // 其实每个柱子上收到的青豆的最大高度,就是其左右侧高度最大值的最小值,所以需要用到左右侧对应每个点的最大值
    // 这里就利用了动态规划来加速
    vector<int> leftHighest(beansHeights.size(), 0);
    vector<int> rightHighest(beansHeights.size(), 0);
    int width = (int)leftHighest.size();

    // 记录左边的最大高度
    leftHighest[0] = beansHeights[0];
    for (int i = 1; i < width; i++) {
        // 取当前点和上一个的最大值
        leftHighest[i] = max(beansHeights[i], leftHighest[i - 1]);
    }
    // 记录右边的最大高度
    rightHighest[width - 1] = beansHeights[width - 1];
    for (int i = width - 2; i >= 0; i--) {
        // 取当前点和上一个的最大值
        rightHighest[i] = max(beansHeights[i], rightHighest[i + 1]);
    }
    // 计数
    int sum = 0;
    for (int i = 0; i < width; i++) {
        //左右侧高度最大值的最小值,减去当前柱子的高度,就是这个点位上积累青豆的数量
        int pointNum = min(leftHighest[i], rightHighest[i]) - beansHeights[i];
        if (pointNum > 0) sum += pointNum;
    }
    return sum;
}
int main() {
    // 处理输入数据
    //vector<int> beansHeights;
    //int tmp;
    //while(cin>>tmp){
      //beansHeights.push_back(tmp);
    //}
    vector<int> beansHeights = {5, 0, 2, 1, 4, 0, 1, 0, 3};
    cout<<countBeans(beansHeights);
}

运行截图

collectbeans.png