主题 4:攒青豆 | 青训营 × 码上掘金

54 阅读1分钟

当青训营遇上码上掘金,我们就可以开始攒青豆了

  • 主题 4:攒青豆

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

    • 在这里,我们使用三种方法对这个问题进行计算。实际上,该问题是一个非常经典的问题,它甚至有一个经典的题目《接雨水》,是必学的好题之一。
      • 方法一:双指针。

        我们需要给每个柱子计算它左边最高的柱子和右边最高的柱子,其中这个柱子所能承接的青豆量就是两者的最小值 - 该柱子的高度。而宽度始终为1 ,因此承接的青豆量就是 两者的最小值 - 该柱子的高度。

        //双指针
        int length = input.length();
        int sum = 0;
        for(int i = 0; i < length; i++){
        //在外层循环中,找到每一个柱子左边最高的柱子,并记录下来
        //应该初始化为当前柱子的高度,而不是随便一个数字例如0
        int l = input[i];
        for(int j = 0; j < i; j++){
          if(l < input[j]) l = input[j];
        }
        
        //在外层循环中,找到每一个柱子右边最高的柱子,并记录下来
        int r = input[i];
        for(int j = length-1; j > i; j--){
          if(r < input[j]) r = input[j];
        }
        
        //每个柱子接的高度是左边最高柱子与右边最高柱子中最矮的柱子 与 该柱子本身之间的差值。
        //不要忘记当得数大于0时,才进行计算。
        if(min(l,r) - input[i] > 0)
          sum += min(l,r) - input[i];
          }
          cout<<sum<<endl;
        
      • 方法二: 动态规划

        在先前的双指针算法里,是遍历到每个柱子的时候,当场计算它的左边最高柱子和右边最高柱子,这样的时间复杂度就是O(n²)。我们可以在初始化时,就计算出每个柱子的左边最高柱子和右边最高柱子,这样实际上就只需要三个并列的for循环,时间复杂度也成功降低为O(n)了

        int length = input.length();
        int sum = 0;
        vector<int> l(length);
        vector<int> r(length);
        //找到左边最高柱子
        l[0] =  input[0];
        for(int i = 1; i < length; i++){
        l[i] = max(l[i-1],input[i])
        }
        
        //找到右边最高柱子
        r[length-1] =  input[length-1];
        for(int i = length-2; i >= 0; i--){
        r[i] = max(r[i+1],input[i])
        }
        for(int i = 0; i < length; i++){
        if(min(l[i], r[i]) - input[i] > 0)
          sum += min(l[i], r[i]) - input[i]
        }
        cout<<sum<<endl;
        return 0;
        
      • 方法三:单调栈

        以上的计算都是计算每一列来算的,而单调栈则是在利用数据结构栈的基础上,计算每一行的总和。

          int length = input.length();
          int sum = 0;
          stack<int>st;
          for(int i = 0; i < length; i++){
            if(st.empty() || input[st.top()] > input[i]){
              st.push(i);
            }else if(input[st.top()] == input[i]){
                st.pop();
                st.push(i);
            }else{
              while(!st.empty() && input[st.top()] < input[i]){
                int temp = input[st.top()];
                st.pop();
                if(min(input[st.top()], input[i]) -  temp > 0){
                  sum += (min(input[st.top()], input[i]) -  temp) * (i - st.top() - 1);
                }
              }
              st.push(i);
            }
          }