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

65 阅读4分钟

当青训营遇上码上掘金

题目描述

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

题目描述

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

思路分析

双指针解法

首先,确定下来按照列来计算。这样宽度一定是1,我们再把每一列的青豆的高度求出来就可以了。

可以看出每一列青豆的高度,取决于,该列左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。

例如求列3的青豆高度,列3左侧最高的柱子是列0,高度为5。列3右侧最高的柱子是列4,高度为4。列3柱子的高度为1。

那么列3的青豆高度为列0和列4的高度最小值减列3高度,即: min(lHeight, rHeight) - height

列3的青豆高度求出来了,宽度为1,相乘就求出列3的青豆体积了。

一样的方法,只要从头遍历一遍所有的列,然后求出每一列青豆的体积,相加之后就是总青豆的体积了。首先从头遍历所有的列,并且要注意第一个柱子和最后一个柱子不接青豆,在for循环中求左右两边最高柱子,最后,计算该列的青豆高度。

因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2)。空间复杂度为O(1)。

动态规划解法

在上面的双指针解法中,我们可以看到只要记录左边柱子的最高高度和右边柱子的最高高度,就可以计算当前位置的青豆面积,这就是通过列来计算。

当前列青豆面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。

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

当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。

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

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

这样就找到递推公式。时间复杂度为O(n),空间复杂度为O(n)。

优化的双指针解法

动态规划的做法中,需要维护两个数组leftMax和rightMax,因此空间复杂度是O(n)。通过优化的双指针解法可以将空间复杂度降到0(1)。

注意到下标i处能接的青豆量由leftMax[i]和rightMax[i]中的最小值决定。由于数组leftMax是从左往右计算,数组rightMax是从右往左计算,因此可以使用双指针和两个变量代替两个数组。

维护两个指针left和right,以及两个变量leftMax和rightMax,初始时left = 0,right = n - 1,leftMax = 0,rightMax = 0。指针left只会向右移动,指针right只会向左移动,在移动指针的过程中维护两个变量leftMax和rightMax的值。

当两个指针没有相遇时,进行如下操作:

  • 使用height[left]和height[right]的值更新leftMax和rightMax的值;
  • 如果leftMax < rightMax,下标left处能接的青豆等于leftMax - height[left],将下标left处能接的青豆量加到能接的青豆总量,然后将left加1(即向右移动一位);
  • 如果leftMax > rightMax,下标right处能接的青豆量等于rightMax - height[right],将下标right处能接的青豆加到能接的青豆总量,然后将right减1(即向左移动一位)。

对加粗部分的补充:

  • left从左向右遍历,right从右向左遍历;
  • 则对left来说,leftMax一定准确,rightMax不一定准确,因为区间(left, right)的值还没有遍历,但是left的rightMax一定 >= right的rightMax,所以只要leftMax < rightMax时,我们不关系left的rightMax是多少了,因为它肯定比leftMax大,我们可以直接计算出left的存水量leftMax - nums[left];
  • 对right来说,rightMax一定准确,leftMax不一定准确,因为区间(left, right)的值还没有遍历,但是right的leftMax一定 >= left的leftMax,所以只要leftMax >= rightMax时,我们不关系right的leftMax是多少了,因为它肯定比rightMax大,我们可以直接计算出right的存水量rightMax - nums[right]。

当两个指针相遇时,即可得到能接的青豆总量。

这种解法时间复杂度为O(n),空间复杂度为O(1)。

代码