当青训营遇上码上掘金
题目
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
解法1:动态规划
对于每一根柱子,算出它头顶上面可以接住多少青豆,把这些数加起来就能得到这一堆柱子总共接的青豆数目。
每一根柱子头顶上可以接多少青豆,取决于它左边所有柱子中最高的那个,和右边所有柱子中最高的那个。俩边一夹,青豆不就跑不出去了吗?
为了可以记录编号为i的柱子左、右两边最高的柱子是多少。我们可以初始化两个数组leftMax[n],rightMax[n]。利用动态规划的思想,第i个柱子左/右两边最高的柱子的高度分别取决于第i-1个柱子和第i+1个柱子。由此可以列出递推公式
leftMax[i]=max(leftMax[i-1],height[i-1])
rightMax[i]=max(rightMax[i+1],height[i+2])
用这两个数组做好记录后,根据木桶原理选出最小的一端,即min = min(leftMax[i],rightMax[i]),然后再和第i个位置的高度height[i]比较。只有min>height[i]时才能接到青豆。然后只需要把每一个柱子接到的青豆加起来就OK了。
时间复杂度为O(n),由于开了两个长度为n的数组故空间复杂度为O(n)
代码如下:
function trap(height: number[]): number {
let sum = 0
let n = height.length
let leftMax = new Array(height.length).fill(0),rightMax = new Array(height.length).fill(0)
for(let i=1;i<n-1;i++){
leftMax[i] = Math.max(leftMax[i-1],height[i-1])
}
for(let i=n-2;i>=0;i--){
rightMax[i] = Math.max(rightMax[i+1],height[i+1])
}
for(let i=1;i<n-1;i++){
let min = Math.min(leftMax[i],rightMax[i])
if(min>height[i]){
sum += (min-height[i])
}
}
return sum
};
解法2:双指针
其实原理类似解法1,但左右柱子的最大高度变成由两个数值变量leftMax,rightMax保管。还需要引入两个新变量left,right记录当前遍历的位置。
代码如下:
function trap(height: number[]): number {
let n = height.length
let sum = 0
let left = 0,right=n-1
let leftMax = 0,rightMax=0
while(left<=right){
leftMax=Math.max(leftMax,height[left])
rightMax=Math.max(rightMax,height[right])
if(leftMax<=rightMax){
sum+=leftMax-height[left++]
}else{
sum+=rightMax-height[right--]
}
}
return sum
};