「青训营 X 码上掘金」主题创作——主题四:攒青豆

82 阅读3分钟

当青训营遇上码上掘金

主题题目描述

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

725ef710a59944d49d0315bece7a1ac1_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

图示样例为:

输入:height = [5,0,2,1,4,0,1,0,3]

输出:17

解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

解题思路与过程

初始思路

通过观察可以发现,当一个位置两端都有比自己更高的柱子时,当前位置就可以用来存储青豆了。而要让攒的青豆最多,则需通过当前位置左右两端最高的柱子进行计算。类似于木桶的短板效应,一个位置能装青豆的最高高度则是两端最高柱子的较小值,即min(lmax[i],rmax[i])min(lmax[i], rmax[i])

能计算出每个位置能装青豆的最高高度,我们就可以对该题目进行解答了,我们先对每个位置的lmaxlmaxrmaxrmax进行处理,通过每个位置的柱子高度和能装青豆的高度,求出该位置能装多少青豆,对每个位置的青豆进行加和即为最后的答案

双指针思路

我们可以使用双指针算法对原始思路的空间复杂度进行优化,可以避免开数组进行lmaxlmaxrmaxrmax的预处理

通过上面的思路我们可以得到,一个位置能装的青豆高度由其左右两端的柱子最大值的较小值决定,那么我们可以将双指针放于数组两端,记录当前的左端最大值和右端最大值,我们每次对最大值较小一端进行操作,计算出当前能装的青豆高度,并将指针向数组中间移动,最终算出所有位置的青豆数,加和得到答案。

解法的时间空间复杂度

初始做法

时间复杂度:O(n)O(n)nn为给出的height数组的长度,无论是计算lmaxlmaxrmaxrmax 还是最后统计答案,我们都只需要对数组进行一次遍历即可

空间复杂度:O(n)O(n),解法过程需要lmaxlmax数组以及rmaxrmax数组,数组长度与给出的height数组相同

双指针做法

时间复杂度:O(n)O(n),双指针分别从数组两端向中间对数组进行一次遍历,双指针移动次数不会超过nn

空间复杂度:O(1)O(1),只需要常数级的额外几个变量

解题代码

初始做法

C++代码:

class Solution {
public:
    int bean(vector<int>& height) {
        int n = height.size(), ans = 0;
        vector<int> lmax(n + 2, 0), rmax(n + 2, 0);

        for(int i = 0; i < n; i++) //计算左端最大值
            if(i != 0)
                lmax[i] = max(lmax[i - 1], height[i - 1]);

        for(int i = n - 1; i >= 0; i--)  //计算右端最大值
            if(i != n - 1)
                rmax[i] = max(rmax[i + 1], height[i + 1]);

        for(int i = 0; i < n; i++)   //统计每列的存豆量
            ans += max(0, min(lmax[i], rmax[i]) - height[i]);
        return ans;
    }
};

Go语言代码:

func max(a int, b int) int {
   if a > b {
      return a
   }
   return b
}

func min(a int, b int) int {
   if a > b {
      return b
   }
   return a
}

func bean(height []int) int {
   lmax := make([]int, len(height))
   rmax := make([]int, len(height))

   for i := 0; i < len(height); i++ {
      if i != 0 {
         lmax[i] = max(lmax[i - 1], height[i - 1])
      }
   }

   for i := len(height) - 1; i >= 0; i-- {
      if i != len(height) - 1 {
         rmax[i] = max(rmax[i + 1], height[i + 1])
      }
   }

   ans := 0
   for i := 0; i < len(height); i++ {
      ans += max(0, min(lmax[i], rmax[i]) - height[i])
   }
   return ans
}

双指针做法

C++代码:

class Solution {
public:
    int bean(vector<int>& height) {
        int ans = 0;
        int lmax = 0, rmax = 0, l = 0, r = height.size() - 1;
        while(l <= r)
        {
            if(lmax < rmax)
            {
                lmax = max(lmax, height[l]);
                ans += lmax - height[l];
                l++;
            }
            else
            {
                rmax = max(rmax, height[r]);
                ans += rmax - height[r];
                r--;
            }
        }
        return ans;
    }
};

Go语言代码:

func max(a int, b int) int {
   if a > b {
      return a
   }
   return b
}

func bean(height []int) int {
   var l, r int = 0, len(height) - 1
   var lmax, rmax, ans int
   for l <= r {
      if lmax < rmax {
         lmax = max(lmax, height[l])
         ans += lmax - height[l]
         l++
      } else {
         rmax = max(rmax, height[r])
         ans += rmax - height[r]
         r--
      }
   }
   return ans
}

码上掘金C++代码

码上掘金Go语言代码