当青训营遇上马上掘金——攒青豆(三种实现方法)

106 阅读3分钟

当青训营遇上码上掘金

题目描述

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

image.png

以下为上图例子的解析: 输入:height = [5,0,2,1,4,0,1,0,3]
输出:17 
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

题解

方法一动态规划

思路分析

对于下标i,该位置能接到的青豆是=min(左边最高主子,右边最高柱子)-height[i]

最简单的做法是对于每一个位置,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的雨水量。假设数组height的长度为N,对每个位置都要向两边扫描并得到最大高度,因此总时间复杂度是O(n^2)。

实际上我们可以用动态规划在O(n)的时间求出这个最大高度。

创建两个长度为n的数组leftMax和rightMax。动态规划计算式如下: 最大高度要么是此时新遍历的柱子,要么是还是前一个位置时的最大高度。

leftMax[i]=max(heighs[i],leftMax[i-1]);

rightMax[i]=max(heighs[i],rightMax[i+1]);

通过正向遍历和反向遍历得到两个最大数组,就可以通过以下式子计算每一个位置所能接的青豆: 左右两边最大柱子中较小的一个减去当前位置柱子的高度。

qingdou+=min(leftMax[i],rightMax[i])-heighs[i];

完整代码

#include <iostream>
#include <vector>
using namespace std;

int Solution(vector<int>);
int main()
{
    vector<int> myVector = {5,0,2,1,4,0,1,0,3};
    int answer = Solution(myVector);
    cout << "总共的青豆是:" << answer << endl;
    return 0;
}
int Solution(vector<int> heighs)
{
    int size=heighs.size();
    vector<int> leftMax(size),rightMax(size);
    leftMax[0]=heighs[0];
    rightMax[size-1]=heighs[size-1];
    for(int i=1;i<size;i++)
    {
        leftMax[i]=max(heighs[i],leftMax[i-1]);
    }
    for(int i=size-2;i>=0;i--)
    {
        rightMax[i]=max(heighs[i],rightMax[i+1]);
    }
    int qingdou=0;
    for (int i = 0; i < size; i++)
    {
        qingdou+=min(leftMax[i],rightMax[i])-heighs[i];
    }
    return qingdou;
    
}

复杂度分析

时间复杂度:O(n),其中 n 是数组height 的长度。计算数组 leftMax 和 rightMax 的元素值各需要遍历数组 height 一次,计算能接的雨水总量还需要遍历一次。

空间复杂度:O(n),其中 n 是数组 height 的长度。需要创建两个长度为 n 的数组 leftMax 和rightMax。

方法二双指针

动态规划的做法中,需要维护两个数组 leftMax 和 rightMax,因此空间复杂度是 O(n)。 但是我们可以用双指针将空间复杂度降到 O(1)。

思路分析

下标 i 处能接的雨水量由 leftMax[i] 和 rightMax[i] 中的最小值决定。由于数组 leftMax 是从左往右计算,数组 rightMax 是从右往左计算,因此可以使用双指针和两个变量代替两个数组。

维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,初始时left=0,right=n−1,leftMax=0,rightMax=0。指针left 只会向右移动,指针 right 只会向左移动,在移动指针的过程中维护两个变量 leftMax 和 rightMax 的值。

当两个指针没有相遇时,进行如下操作:

height[left] 和 height[right] 的值更新 leftMax 和 rightMax 的值;

如果 height[left]<height[right],则必有 leftMax<rightMax,下标 left 处能接的青豆等于 leftMax−height[left],将下标 left 处能接的青豆加到能接的雨水总量,然后将left + 1(即向右移动一位)。

如果 height[left]>height[right],则必有 rightMax>leftMax,下标 right 处能接的青豆等于 rightMax−height[right],将下标 right 处能接的青豆加到能接的雨水总量,然后将right - 1(即向左移动一位)。

当两个指针相遇时,即可得到能接的青豆总量。

完整代码

#include <iostream>
#include <vector>
using namespace std;

int Solution(vector<int>);
int main()
{
    // vector<int> myVector={2,0,2};
    vector<int> myVector = {5, 0, 2, 1, 4, 0, 1, 0, 3};
    // vector<int> myVector = {0,1,0,2,1,0,1,3,2,1,2,1};
    int answer = Solution(myVector);
    cout << "总共的青豆是:" << answer << endl;
    return 0;
}
int Solution(vector<int> height)
{
    int qingdou = 0;
    int size = height.size();
    int left = 0, right = size - 1;
    int leftMax = 0, rightMax = 0;
    for (int i = 0; i < size; i++)
    {
        if (leftMax < rightMax)
        {
            leftMax = max(leftMax, height[left]);
            qingdou += leftMax - height[left];
            left++;
        }
        else
        {
            rightMax = max(rightMax, height[right]);
            qingdou += rightMax - height[right];
            right--;
        }
    }
    return qingdou;
}

复杂度分析

时间复杂度:O(n),其中 nn 是数组 height 的长度。两个指针的移动总次数不超过 n。

空间复杂度:O(1)。只需要使用常数的额外空间。