题目如下
最优解法
如果刷过leetcode的小伙伴,会发现这题和leetcode上接雨水题是一样的42. 接雨水 - 力扣(LeetCode)
本题的最优解是基于双指针来实现的,时间复杂度为,空间复杂度为,做法大约是同时维护一个left指针和一个right指针,每次都会选择一个较矮的指针,将它向靠拢的方向移动,并记录移动过程中的最大高度leftMax与rightMax。每次移动指针时,都会将此处柱子的高度与左右最大高度leftMax、rightMax中的较小值进行对比,如果存在空余空间,那么这个空余空间就是这个柱子处可以攒到的青豆数。当left指针与right指针相遇的时候,运行就结束了。以上是对最优解法的简单描述,更详细的解答可以移步leetcode题解。
一种花里胡哨的解法
首先声明,这种解法不是最优解,甚至他的时间复杂度到了,把他分享出来,是希望能够有一个不一样的视角来看待这道题。
思路
这是一种插柱子的做法,首先,我们需要将全部的柱子按照高度排序。 然后,我们首先取出最高的两个柱子(你问我只有一个柱子怎么办?很明显返回0),这两个柱子就是我们当前的边界,这个区域内就是我们目前能攒到的全部青豆。 以图中的数据为例:
当前柱子 5 - - - 4 - - - - 能攒青豆数 0 4 4 4 0 0 0 0 0 左边界为0,右边界为4,当前总共的青豆数为12
然后,按照从高到矮的顺序,依次取出各个柱子,并插入到图上。
如果插入的柱子在左右边界范围外,那么这个柱子可以让我们接收到更多的青豆,其数量为这个柱子与其最近的旧边界之间的全部区域,我们增加总青豆数,并扩展边界。
当前柱子 5 - - - 4 - - - 3 能攒青豆数 0 4 4 4 0 3 3 3 0 左边界为0,右边界为8,当前总共的青豆数为21
如果新插入的柱子在边界范围内,那么插入这个柱子会挤出一部分青豆(因为空间被柱子占用了),挤出的数量为柱子的高度。
当前柱子 5 - 2 - 4 - - - 3 能攒青豆数 0 4 2 4 0 3 3 3 0 左边界为0,右边界为8,当前总共的青豆数为19
继续该过程,直到所有的柱子都被插入到图中,得到的答案就是我们能攒到的青豆数量。
正确性证明
这里简单证明正确性。由于我们所有的柱子都是按照从高到矮的顺序插入的,因此,在扩展边界的情形中,离新插入的柱子最近的边界一定高于这个新插入的柱子,因此他们之间的区域全都能接满高度为新插入柱子的青豆;在挤出青豆的情形中,新插入的柱子一定会低于他左右两边的边界,因此挤出青豆的数量就是这个柱子的高度。
复杂度
很明显,时间复杂度为(排序部分),空间复杂度为(用来保存排序数组)
代码
struct Item {
int h;
int idx;
};
int gather_beans(vector<int> &height) {
int n = height.size();
if (n <= 1)
return 0;
vector<Item> pos;
for (int i = 0; i < n; i++) {
pos.push_back(Item{height[i], i});
}
sort(pos.begin(), pos.end(), [](Item &a, Item &b) {
return a.h > b.h;
});
Item l = pos[0], r = pos[1];
if (l.idx > r.idx)
swap(l, r);
int total = min(l.h, r.h) * (r.idx - l.idx - 1);
for (int i = 2; i < n; i++) {
Item item = pos[i];
if (l.idx < item.idx && item.idx < r.idx) {
total -= item.h;
} else if (item.idx < l.idx) {
total += item.h * (l.idx - item.idx - 1);
l = item;
} else {
total += item.h * (item.idx - r.idx - 1);
r = item;
}
}
return total;
}