青训营-攒青豆题目的一种简单思路

62 阅读2分钟

当青训营遇上码上掘金
这次的青训营的题目攒青豆实际上和LeetCode上的接雨水题目是一样的,感兴趣的同学可以去查看更多的精彩的解法,我在这仅提供我自己的一个比较容易想到的思路。

题目 攒青豆

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

image.png 从图中我们可以很直观的看出,我们可以把整个区间划分为两个小的区间,即区间 [0,4][4,8]
根据木桶效应,区间之间的水平高度取决于较短的一边,所以第一个区间的豆子高度为4,第二个区间的豆子高度为3
这样我们就可以很轻易的根据豆子的高度以及区间内的其他柱体的高度求得区间内的所有青豆的数量, 公式为 n=L区间长度H青豆高度S区间内其他柱体高度和n=L_{区间长度} *H_{青豆高度}-S_{区间内其他柱体高度和}

例如
区间1的青豆数量为 (4-0-1)*4-(2+1)=9
区间2的青豆数量为 (8-4-1)*3-(1)=8
总数即为9+8=17
由此我们可以很轻易的写出求取区间内豆子数的代码:

int getPart(vector<int>& height, int left, int right)
{
    //注意需要判断区间内无豆子的情况
    if(right - left < 2)
        return 0;
    int solid = 0;
    for (int i = left + 1; i < right; ++i)
        solid += height[i];
    return (right - left - 1) * min(height[left], height[right]) - solid;
}

接下来我们只需要把给定的区间分成这样一个个像刚刚的区间1、2这样的小区间就好
即,划分好的区间应该具有以下特性: 区间的左右边界是整个区间内最长的两条边

具体的计算过程如下:

int trap(vector<int>& height) {
    if(height.size() < 2)
        return 0;
    int left, right, mid;
    int result = 0;
    mid = max_element(height.begin(), height.end()) - height.begin();
    //向左计算
    right = mid;
    left = max_element(height.begin(), height.begin() + right) - height.begin();
    while(left > 0)
    {
        result += getPart(height, left, right);
        right = left;
        left = max_element(height.begin(), height.begin() + right) - height.begin();
    }
    result += getPart(height, left, right);
    left = mid;
    right = max_element(height.begin() + mid + 1, height.end()) - height.begin();
    while(right < height.size() - 1)
    {
        result += getPart(height, left, right);
        left = right;
        right = max_element(height.begin() + left + 1, height.end()) - height.begin();
    }
    result += getPart(height, left, right);
    return result;
}

首先找到区间内的最长边,以此作为中点,再向左寻找下一条最大边,以此构成一个小的新区间S1;在统计完新区间后,再将S1的左边界作为新的起点,继续向左探寻下一条最大边,直到向左达到边界;向右划分同理,最后我们就成功获得了区间内的青豆总数

由于在划分区间的过程中需要不断的寻找最大值,同时求出区间内的青豆数量还需要再扫描一遍区间,所以该算法的时间复杂度为O(N2)O(N^2),空间复杂度为O(1)O(1)