「青训营 X 码上掘金」主题4:攒青豆

145 阅读2分钟

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

725ef710a59944d49d0315bece7a1ac1_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

编程小白,如有错误,还请见谅。
看见这个题目之后,最先想到的是木桶原理。
大概知道是模拟题,于是抱着试试的心态开始写。
一开始的大概思路是:从未遍历的最长的柱子开始,找到一个次长的柱子,计算这个最长和次长柱子之间能容纳的青豆数量,最后减去中间短柱子所占的青豆数量。

#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int q[N], n;

int main()
{
    cin >> n;

    for (int i = 0; i < n; i++)
        cin >> q[i];

    int re = 0; // re表示接住青豆的数量

    for (int i = 0; i < n - 1;)
    {
        // max 表示从遍历过的柱子的下一根到最后一根之中最长的柱子
        
        int max = i + 1;
        // 寻找最大值对应的下标
        for (int j = i + 1; j < n; j++)
            if (q[j] > q[max])
                max = j;
        //两长柱子之间能接住的最大青豆数量(不算中间的柱子)
        int min = q[i] < q[max] ? q[i] : q[max];
        re += (max - i - 1) * min;
        //遍历减掉两个长柱子之间短柱子占用的青豆数量和
        for (int j = i + 1; j < max; j++)
            re -= q[j];
        //
        i = max;
    }

    cout << re << endl;

    return 0;
}

十分钟搞定!
样例通过! 直到我遇到第一个困难,突然发现输入:

5
1 0 2 0 3

此时输出为 1
输入:

5
1 2 3 4 5

输出竟然是 -6 !

经过一系列的调试和分析之后发现,出现这种结果是由于我最开始的思路是基于样例,它的整体9根柱子处于递减趋势,因此可以找长度相对小的柱子计算。可是我的代码遇到像[1,2,3]这样具有递增趋势的数据时,找不到相对第一个更小的数据了,导致结果完全错误。

于是,又经过了漫长的思考 two thousand years later~
我发现既然我的代码只能处理递减趋势的数据,不如把数据都变成递减的来计算。
就分为以下三步:

  1. 找到数据中最大的数字all_max及其下标index
  2. 将index前一部分存放在新数组k当中,其余留在数组q中。
  3. 此时k为递增趋势的数组,所以用reverse翻转即可。

最后,分别计算k数组的index前的部分以及q数组的index后的部分即可。(即计算k[0]到k[index]部分和q[index]和q[n-1]部分,注意边界!)
代码如下:

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
int q[N], k[N];
int n;      // 输入柱子的数量
int re = 0; // re表示接住青豆的数量

int main()
{
    cin >> n;
    // all_max表示所有柱子中最高柱子的长度
    int all_max = 0, index = 0;
    for (int i = 0; i < n; i++)
    {
        cin >> q[i];
        all_max = all_max > q[i] ? all_max : q[i];
        if (all_max == q[i])
            index = i;
    }
    // 将q数组复制给k数组
    memcpy(k, q, sizeof(q));

    // 在最大值对应的下标之前进行翻转操作
    reverse(&k[0], &k[index + 1]);

    // 接下来分别计算k数组前一部分和q数组后一部分
    // K:
    for (int i = 0; i < index ;)
    {
        // max 表示从遍历过的柱子的下一根到最后一根之中最长的柱子
        int max = i + 1;
        // 寻找最大值对应的下标
        for (int j = i + 1; j < index+1; j++)
            if (k[j] > k[max])
                max = j;
        // 两长柱子之间能接住的最大青豆数量(不算中间的柱子)
        int min = k[i] < k[max] ? k[i] : k[max];
        re += (max - i - 1) * min;
        // 遍历减掉两个长柱子之间短柱子占用的青豆数量和
        for (int j = i + 1; j < max; j++)
            re -= q[j];
        //更新起点
        i = max;
    }

    //q:
    for (int i = index; i < n - 1;)
    {
        // max 表示从遍历过的柱子的下一根到最后一根之中最长的柱子
        int max = i + 1;
        // 寻找最大值对应的下标
        for (int j = i + 1; j < n; j++)
            if (q[j] > q[max])
                max = j;
        //两长柱子之间能接住的最大青豆数量(不算中间的柱子)
        int min = q[i] < q[max] ? q[i] : q[max];
        re += (max - i - 1) * min;
        //遍历减掉两个长柱子之间短柱子占用的青豆数量和
        for (int j = i + 1; j < max; j++)
            re -= q[j];
        //更新起点
        i = max;
    }

    cout << re << endl;

    return 0;
}

代码在码上掘金也可以正常运行 至此,答案终于被完善。
一道小题,虽然花费了大量的时间和精力,但自己的收获才是无价之宝。 此题可以使用单调栈和dp优化,需要花时间再研究研究 😉。