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

72 阅读2分钟

当青训营遇上码上掘金

题目

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

分析

这是一道非常经典的算法题(我在高一的时候就见过了。。。),可以在 Leetcode 上面找到题目的原型。

思路一

如果我们以为单位看,那么如果一行中的某一列的高度低于行高,且其左右均存在高于或等于行高的列,那么这个这个格子将会攒一个单位的青豆,否则不会攒青豆。

遍历每一行每一列,可以找到全部可以攒青豆的格子。

思路二

如果我们以为单位看,那么如果某一列的左侧和右侧均存在高于这一列的行高的列,那么这一列会攒 min(maxLeft, maxRight) - height单位个青豆。

比如样例中的第二列的行高(0),低于左侧最大值(5)且低于右侧最大值(4),那么它会积攒 4 个青豆。

遍历每一列,可以得到接住青豆的总数

实现

我最后选择了思路二(我认为更好实现)

源码

public class Solution {
    public int trap(int[] height) {
        int sum = 0;
        for (int i = 1; i < height.length - 1; i++) {
            int left = 0;
            int right = 0;
            for (int j = i + 1; j <= height.length - 1; j++) {
                if (height[j] > right) {
                    right = height[j];
                }
            }
            for (int j = i - 1; j >= 0; j--) {
                if (height[j] > left) {
                    left = height[j];
                }
            }
            int min = Math.min(left, right);
            sum += min > height[i] ? min - height[i] : 0;
        }
        return sum;
    }
}

复杂度

  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(1)O(1)

优化

很显然这里还有很大的优化空间,因为每一次判断一列攒青豆的数量时,都要重新判断这一列左右最高的墙,非常浪费时间。

而且,同样显然的是,如果某一列右侧的最高的列不是其右侧临近的一列时,其右侧临近的一列的右侧的最高的列,应该与之相同。

所以我们可以使用动态规划,先分别算出maxLeft[i] = Math.max(maxLeft[i-1], height[i])maxRight[],再for遍历两个新数组。

复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

再优化

我们构造的数组中大部分数据是重复的(局部最大值),这意味着我们可以使用两个数分别代替局部数组,以减少空间复杂度。

可以使用双指针实现,篇幅有限,这里就不演示了。

复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(1)O(1)