- 当青训营遇上码上掘金
- 参与这次主题活动青训营参加的是前端,但是呢,之前一直学习后端,所以选取了一个后端同学的题目来做一做。
题目
- 现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

分析
- 很眼熟啊,这题目不就是接雨水吗?轻车熟路直接写出来当时的写法,42. 接雨水 - 力扣(LeetCode)
- 这道题其实代码不难,但是思路想法确实比较巧妙,当然有一个核心的思路就是当前柱子接的豆子数量取决于豆子左右两边最高的柱子与当前柱子的差值,好好想一想是不是呢?
- 伪代码
当前柱子豆子数量 = Min(左边最高柱子,右边最高柱子) - 当前柱子高度
- 明白上面的核心思想后是不是一目了然呢?我们只需要挨个对每个柱子的数量进行累加就可以算出总的豆子数量。
实现
实现一
- 最容易想到的是不是我们遍历每一个柱子,然后找到每一个柱子的左边最高和右边最高(左右两边的柱子因为确实左边和右边所以不用计算)
- 实现思路:
- 遍历数组,从第二个元素开始,到倒数第二个元素结束
- 每次遍历,都要找到当前元素左边的最大值,和右边的最大值
- 然后取两个最大值的最小值,减去当前位置的值,就是当前元素能接的青豆数目
- 累加每个位置能接的青豆数目,就是最终的结果
function getGreenBeans1(arr) {
let ans = 0;
for(let i=1; i<arr.length-1; i++){
let max_left = 0;
let max_right = 0;
for(let j=i; j>=0; j--){
max_left = Math.max(max_left, arr[j]);
}
for(let j=i; j<arr.length; j++){
max_right = Math.max(max_right, arr[j]);
}
ans += Math.min(max_left, max_right) - arr[i];
}
return ans;
}
console.log(getGreenBeans1([5,0,2,1,4,0,1,0,3]));
- 这样我们就实现了接青豆的代码了。但是上面的代码可以看出我们对每个位置都去找左边最大值和右边最大值,这样时间复杂度和空间复杂度都是O(n^2),可不可以继续优化呢?当然可以!!
- 毕竟鲁迅说过忘记过去的人注定要重蹈覆辙(狗头保命,鲁迅表示我没说过!!)
实现二
- 这次我们引入动态规划的思想,我们记住每个元素左边的最大值和右边的最大值并且同步更新它们,这里代码我就只贴左边一半啊,完整代码可以去码上掘金去获取,结尾我也将附上链接。
- 当然因为柱子中必定存在一个最高的柱子,当我们在最高柱子左边时,那我们右边最高就是maxIdx,反之亦然。
- 实现思路:
- 优化找左边最大值和右边最大值的过程
- 先找到数组中的最大值
- 遍历最大值左边的元素,找到每个位置左边的最大值,与最大值的差值就是当前元素能接的青豆数目
- 遍历最大值右边的元素,找到每个元素右边的最大值,与最大值的差值就是当前元素能接的青豆数目
- 累加每个位置能接的青豆数目,就是最终的结果
function getMaxBar(rainArr) {
let maxIdx = 0;
for (let i = 0; i < rainArr.length-1; i++) {
if(rainArr[i] >= rainArr[maxIdx]){
maxIdx = i;
}
}
return maxIdx;
}
function getLeftGreenBeans(rainArr, maxIdx) {
let ans = 0;
let max_left = 0;
for (let i = 0; i < maxIdx; i++) {
if(rainArr[i] >= max_left){
max_left = rainArr[i];
}else{
ans += max_left - rainArr[i];
}
}
return ans;
}
- 以上就是我此次参与 接青豆 | 「青训营 X 码上掘金」 活动的实现思路以及代码
