当青训营遇上码上掘金,有这样一个接青豆的问题,现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)。
在看这道题之前呢,我们需要理清几个问题,首先青豆在什么情况下能被接到?就题意而言,当至少存在三个柱子,它们的高度用数组表示为[a,b,c],当a>b&&c>b时,我们才能确保能接到青豆,而接到青豆的数量就是min(a,c)-b。这里引出第二个问题,接青豆的最大数目是由谁来决定的,就本题而言,有点类似于木桶理论,能接青豆的数量取决于最中间的底--b以及两个柱子中较短的一根。
这里我们遵循归纳法的原则,将三个柱子变为四个柱子[a,b,c,d],那么此时要想接到青豆又当如何呢,不难想到,必须中间的bc,至少有一个是既小于a,又小于d的,例如[5,0,4,3],可以在0处接豆,而这个特殊的例子里,很明显0只能接4个豆,计算原理是min(5,4)-0,可以看到此时的计算与最右边的3是无关的。到这里我们可以思考有n个柱子的情况,事实上每个柱子所在位置能否装青豆取决于这个位置左边最长的柱子和右边最长的柱子能否都长过它本身,这样假设一个柱子高度为x,它左边最高为a,右边最高为b,那么它能装的豆子就是max(a,b)-x。当然了,为了不出现负数,我们可以让这个所谓的左边右边包括x本身,这样,a,b>=0,也就是最少装0个豆子。那么接下来就是算出每个柱子左边最大的和右边最大的,可以从左往右,再从右往左,都遍历一次即可,所得的数据刚好可以定义两个数组来保存,
for (int i = 1; i <len ; i++) {
leftMax[i]=Math.max(leftMax[i-1],height[i]);
}
for (int i = len-2; i >=0 ; i--) {
rightMax[i]=Math.max(rightMax[i+1],height[i]);
}
之后计算再次遍历,把每一次的max(leftMax[i],rightMax[i])-height[i]加起来即可,基本算是动态规划的一种做法。 使用动态规划,时间复杂度为o(n),因为只进行了三次遍历,空间复杂度由于我们定义了两个大小为n的数组,所以复杂度为O(n)。琢磨下来时间复杂度到o(n)已经很难优化,空间复杂度似乎还有优化空间。我们在做dp动态规划时,很明显是分成了两步走,第一步先计算左边的最大值和右边的最大值并用数组保存,第二步才是计算能装青豆的数量。那么考虑将两步一起操作,一边计算青豆值,一边计算左右两边的最大值。可以使用双指针(left,right)的方法。初始时,左指针指向height[0],右指针指向height[len-1],比较左右指针的高度大小,如果左边小,就说明青豆取决于左边,计算maxLeft-height[left],同时将左指针右移,计算max(maxLeft,height[left]),计算出新的左边最大值,反之说明青豆取决于右边,计算maxRight-height[i],同时将右指针左移,计算max(maxRight,height[right]),计算出新的右边最大值。核心代码如下:
for {
if maxLeft < maxRight {
ans += maxLeft - arr[left]
left++
if left >= right {
break
}
maxLeft = max(maxLeft, arr[left])
} else {
ans += maxRight - arr[right]
right--
if left >= right {
break
}
maxRight = max(maxRight, arr[right])
}
}
双指针的方法最终空间复杂度为o(1),也是我完成的最终代码,具体完整代码链接如下:
code.juejin.cn/pen/7192910… ps:代码设置为acm输入模式,但是掘金平台似乎无法执行输入,可复制到本地验证代码,另附本题源题:leetcode42 接雨水:leetcode.cn/problems/tr…