当青训营遇上码上掘金
题目:攒青豆
题目描述:现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
编程小白,如有错误,还请见谅。
看见这个题目之后,最先想到的是木桶原理。
大概知道是模拟题,于是抱着试试的心态开始写。
一开始的大概思路是:从未遍历的最长的柱子开始,找到一个次长的柱子,计算这个最长和次长柱子之间能容纳的青豆数量,最后减去中间短柱子所占的青豆数量。
#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~
我发现既然我的代码只能处理递减趋势的数据,不如把数据都变成递减的来计算。
就分为以下三步:
- 找到数据中最大的数字all_max及其下标index
- 将index前一部分存放在新数组k当中,其余留在数组q中。
- 此时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优化,需要花时间再研究研究 😉。