当青训营遇上码上掘金-主题4-攒青豆

74 阅读3分钟

当青训营遇上码上掘金

1、题目

现有 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 个单位的青豆。

2、单调栈解题思路

单调栈的经典题目,接雨水的改编版。 首先,接雨水需要三个柱子,即形成凹槽,所以左右柱子肯定大于中间的柱子。要找到这样的组合,就想到了单调栈。

  • 单调栈:从栈尾到栈头从大到小,
  • 关键点是如果当前要添加的元素大于栈头元素,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
  • 第二个关键点是如何计算雨水面积,设从左向右三个柱子高度分别为left,mid,right, 则高度h = min(left, right) - mid, 宽度w = right_index - left_index - 1;
  • 因为可能会遇到5 4 3 2 5 这种凹槽,所以需要循环判断,不断填充短的凹槽,如上,先填充3 2 5 的凹槽组合,然后是4 3 5, 最后是5 4 5的组合凹槽。

3、c++ 代码

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

//单调栈思想
//看做三个柱子构成的凹槽,单调栈从尾部到栈头,从大到小
int fun(vector<int> height){
    stack<int> st;
    if(height.size() <= 2) return 0;
    //st存放索引
    st.push(0);
    int ans = 0;
    for(int i = 1; i < height.size(); i++){
      int num = height[i];
      while(!st.empty() && num > height[st.top()]){
          //num 为右边界,top()为中间,第二个栈顶元素为左边界
          //mid 为索引位置
          int mid = st.top();
          st.pop();
          if(!st.empty()){
              int h = min(num, height[st.top()]) - height[mid];
              int w = i - st.top() - 1;
              ans += (h * w);
          }
         
      }
       st.push(i);
    }
    return ans;
}
int main() {
    vector<int> height = {5,0,2,1,4,0,1,0,3};
    cout<<fun(height)<<endl;
  return 0;
}

链接: code.juejin.cn/api/raw/718…

4、复杂度分析

空间 O(n) ; 时间 O(n) , n 为输入数组规模

5、其他方案补充

  • 双指针思想: 核心思想即对于每一列柱子,找出其两侧最高的柱子,就可以得到它能存储的雨水量。注意第一个柱子和最后一个柱子不能接水,不计算。时间复杂度O(n^2)

  • 进一步优化: 为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)。这样就避免了重复计算,这就用到了动态规划。

  • 动态规划:当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。

    即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);

    从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);

    时间和空间复杂度O(n)。