当青训营遇上码上掘金 | 攒青豆

78 阅读2分钟

当青训营遇上码上掘金

攒青豆

问题简介

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

解题思路

image.png

  1. 针对任一个柱子0<k<n0<k<n,若存在一个0<i<k0<i<kk<j<nk<j<n,并且min(i,j)>length(k)min(i,j)>length(k),那么撒下青豆后,柱子kk的顶部会有青豆堆积,设堆积的数量为x=min(i,j)length(k)x=min(i,j)-length(k),所以问题转化为求每一个柱子所能堆积的青豆的最大数量,并求和。
  2. 因为x=min(i,j)length(k)x=min(i,j)-length(k),柱子的高度固定,求xx最大值即为求max(min(i,j))max(min(i,j)),所以问题转换为求每一个柱子前后比该柱子高的柱子对中最小值的最大值。
  3. 转换一下就是求柱子kk之前的最大值premaxpre_max,以及柱子之后的最大值backmaxback_max,然后求着两个值的最小值几为柱子kkmax(min(i,j))max(min(i,j)),然后减去柱子的长度,即求得该柱子最终可以堆积的青豆数量。
  4. 边界条件,显然00号和n1n-1号柱子不可能堆积青豆,为了方便可以在-1位置和n位置加上一个高度为0的柱子。
  5. 将每个柱子可堆积青豆的数量加起来就是按该排列最终能接到的青豆数量.

例:下标为6的柱子,高度为1

  • 小于下标6的最高柱子为0号柱子,高度为5
  • 大于下标6的最高柱子为8号柱子,高度为3
  • min(5,3)=3min(5,3)=3,可以堆积青豆数量为3-1=2

算法分析

时间复杂度

  • 每个柱子的前后最大柱子值,都可以通过遍历一次数组获得。
  • 计算最大可堆积的青豆数量和数组求和只需要遍历一次数组。

所以时间复杂度为o(n)o(n)

空间复杂度

  • 只需要一个pre_max和一个back_max的额外空间开销。

所以空间复杂度为o(n)o(n)

代码示例

const svg = await (await fetch('code.juejin.cn/api/raw/719…); document.body.innerHTML = svg;

#include<iostream>
#include<vector>

using namespace std;

int trap(vector<int>& height) {
    int n = height.size();
    vector<int> pre(n,-1); 
    vector<int> back(n,n);
    vector<int> d(n,0);
    if(height.size()<=2) return 0; // 因为柱子是连续的,2个柱子无法形成一个容器空间
    int m = -1;
    for(int i = 0;i<n;i++){// 该柱子之前比该柱子高的柱子,m为该柱子之前最高柱子下标
        if(m!=-1&&height[i]<height[m]) pre[i] = m;
        else m = i;
    }
    m = -1;
    for(int i = n-1;i>=0;i--){//这里是用来寻找该点后面第一个大于其值的点,m为该柱子之后最高柱子下标
        if(m!=-1&&height[i]<height[m]) back[i] = m;
        else m = i;
    }
    int ans = 0;
    for(int i = 0;i<n;i++){
        if(pre[i]==-1||back[i]==n) continue;
        else{
            ans += min(height[pre[i]],height[back[i]])-height[i];
        }
    }
    return ans;
}
int main(){
    int n = 0;
    cin>>n;
    vector<int> height(n);
    for(int i = 0;i<n;++i){
        cin>>height[i];
    }
    cout<<trap(height);
}