当青训营遇上码上掘金之“攒青豆”
题目
主题 4:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
分析
解法一:动态规划
记数组height为柱子高度,对于下标 i ,接住的青豆的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的青豆量等于下标 i 处的水能到达的最大高度减去 height [ i ]。
很容易想到,对于数组 height 中的每个元素,我们需要分别向左和向右寻找两边的最大高度,然后根据查找结果得到此处能接到的青豆数量。由于对于每个元素,都需要进行查找,因此总时间复杂度是 O ( n 2 ) 。
以上做法时间复杂度太高,可以使用空间换时间的方法优化。可以使用两个数组记录每个下标两边的最大高度,因此避免对每个下标位置进行的两边扫描,可以在 O ( n ) 的时间内得到能接的青豆总量。这样的做法采用了动态规划的思想。
创建两个长度为 n 的数组 leftMax和 rightMax 。对于 0 ≤ i < n 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中, height 的最大高度, rightMax[i] 表示下标 i 及其右边的位置中, height 的最大高度。
显然, leftMax [ 0 ] = height [ 0 ],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:
当 1 ≤ i ≤ n − 1 时, leftMax [ i ] = max ( leftMax [ i − 1 ] , height [ i ] ) ; 当 0≤i≤n−2 时rightMax[i]=max(rightMax[i+1],height[i])。 因此可以正向遍历数组 height得到数组 leftMax的每个元素值,反向遍历数组height 得到数组 rightMax 的每个元素值。
在得到数组 leftMax和 rightMax 的每个元素值之后,对于 0 ≤ i < n 0≤i<n,下标 i 处能接的青豆量等于 min ( leftMax [ i ] , rightMax [ i ] ) − height [ i ] 。遍历每个下标位置即可得到能接的青豆总量。
#include <iostream>
#include <vector>
using namespace std;
int solution(const vector<int> height){
int n = height.size();
if (n == 0) {
return 0;
}
vector<int> leftMax(n);
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = max(leftMax[i - 1], height[i]);
}
vector<int> rightMax(n);
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
int main(){
cout << "请输入柱子的高度:";
vector<int> nums;
int temp;
while (cin>>temp){
nums.push_back(temp);
if(cin.get()=='\n') break;
}
cout << "可以接住的青豆数量为:" << solution(nums) << "个" << endl;
}
解法二:单调栈
维护一个单调递减栈,栈里保存元素的下标,当栈中的元素大于等于两个时,遇到下一个大于栈顶元素的元素,这个元素与栈顶的两个元素组成的区域就能接住的青豆,能接住青豆的量就是这三个元素围成的区域面积。
#include <iostream>
#include <vector>
using namespace std;
int solution(const vector<int> height){
int ans = 0;
stack<int> st;
for (int i = 0; i < height.size(); i++)
{
while (!st.empty() && height[st.top()] < height[i])
{
int cur = st.top();
st.pop();
if (st.empty()) break;
int l = st.top();
int r = i;
int h = min(height[r], height[l]) - height[cur];
ans += (r - l - 1) * h;
}
st.push(i);
}
return ans;
}
int main(){
cout << "请输入柱子的高度:";
vector<int> nums;
int temp;
while (cin>>temp){
nums.push_back(temp);
if(cin.get()=='\n') break;
}
cout << "可以接住的青豆数量为:" << solution(nums) << "个" << endl;
}