当青训营遇上码上掘金-攒青豆-一种不一样的解法

83 阅读3分钟

题目如下

image.png

最优解法

如果刷过leetcode的小伙伴,会发现这题和leetcode上接雨水题是一样的42. 接雨水 - 力扣(LeetCode) 本题的最优解是基于双指针来实现的,时间复杂度为O(n)O(n),空间复杂度为O(1)O(1),做法大约是同时维护一个left指针和一个right指针,每次都会选择一个较矮的指针,将它向靠拢的方向移动,并记录移动过程中的最大高度leftMaxrightMax。每次移动指针时,都会将此处柱子的高度与左右最大高度leftMaxrightMax中的较小值进行对比,如果存在空余空间,那么这个空余空间就是这个柱子处可以攒到的青豆数。当left指针与right指针相遇的时候,运行就结束了。以上是对最优解法的简单描述,更详细的解答可以移步leetcode题解。

一种花里胡哨的解法

首先声明,这种解法不是最优解,甚至他的时间复杂度到了O(logn)O(logn),把他分享出来,是希望能够有一个不一样的视角来看待这道题。

思路

这是一种插柱子的做法,首先,我们需要将全部的柱子按照高度排序。 然后,我们首先取出最高的两个柱子(你问我只有一个柱子怎么办?很明显返回0),这两个柱子就是我们当前的边界,这个区域内就是我们目前能攒到的全部青豆。 以图中的数据为例:

当前柱子5---4----
能攒青豆数044400000

左边界为0,右边界为4,当前总共的青豆数为12

然后,按照从高到矮的顺序,依次取出各个柱子,并插入到图上。

如果插入的柱子在左右边界范围外,那么这个柱子可以让我们接收到更多的青豆,其数量为这个柱子与其最近的旧边界之间的全部区域,我们增加总青豆数,并扩展边界。

当前柱子5---4---3
能攒青豆数044403330

左边界为0,右边界为8,当前总共的青豆数为21

如果新插入的柱子在边界范围内,那么插入这个柱子会挤出一部分青豆(因为空间被柱子占用了),挤出的数量为柱子的高度。

当前柱子5-2-4---3
能攒青豆数042403330

左边界为0,右边界为8,当前总共的青豆数为19

继续该过程,直到所有的柱子都被插入到图中,得到的答案就是我们能攒到的青豆数量。

正确性证明

这里简单证明正确性。由于我们所有的柱子都是按照从高到矮的顺序插入的,因此,在扩展边界的情形中,离新插入的柱子最近的边界一定高于这个新插入的柱子,因此他们之间的区域全都能接满高度为新插入柱子的青豆;在挤出青豆的情形中,新插入的柱子一定会低于他左右两边的边界,因此挤出青豆的数量就是这个柱子的高度。

复杂度

很明显,时间复杂度为O(logn)O(logn)(排序部分),空间复杂度为O(n)O(n)(用来保存排序数组)

代码

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;
}