码上掘金之“攒青豆”

48 阅读2分钟

当青训营遇上码上掘金之“攒青豆”

题目

主题 4:攒青豆

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

攒青豆.png

分析

解法一:动态规划

记数组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;
}
​
​