当青训营遇上码上掘金
题目描述
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析: 输入: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)。只需要使用常数的额外空间。