算法-接雨水的个人理解

题目描述

题目链接: . - 力扣(LeetCode)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

思考过程

1. 能接雨水的量,取决于左右柱子中较小的那个(短板效应,长的接不住)

2. 取出较小柱子的值,还需要减去柱子的高度(也就是对应的height)

3. 将每个可以容纳雨水的格子累加起来,就是题解

解法

有了思路,我们就开始尝试按照思路写解法,最容易想到的肯定就是暴力循环,两边分别找到最大值,然后把得到的结果叠加,咱们先试试

/**
 * @param {*} height
 * @return {*}
 */
function tarp (height) {
    // 初始化最大能接雨水
    let result=0;
    // 遍历数组元素
    for(let i=0;i<height.length;i++)
    {
       // 初始化左边与右边最大元素为当前元素
       let leftMax=height[i],rightMax=height[i];
       // 遍历当前元素左边所有元素,找到最大值
       for(let left=i-1;left>=0;left--)
       {
            if(height[left]>leftMax){
                leftMax=height[left];
            }
       }
       // 遍历当前元素右边所有元素,找到最大值
       for(let right=i+1;right<height.length;right++) 
       {
            if(height[right]>rightMax)rightMax=height[right];
       }
       // 计算其位置能接的雨水量,并将其相加
       result+=Math.min(leftMax,rightMax)-height[i];
    }
    // 返回结果
    return result;

}

仔细观察代码,咱们可以发现暴力算法,对于每个元素我们都要镜像两次遍历(左,右分别寻找),复杂度无疑来到了O(n^2),那有没有办法可以直接知道两边的最大值,然后通过O(n)就能计算出总量

我们是否可以用一个元素记录最大值max,然后分别用左右两个数组记录左右两个柱子的最大值,这样我们就能在O(n)的时间得到每个位置两侧的最大高度

 想到了咱们就试试 !

const collectRainWater = (height) => {
    // 定义最大值
    let max = 0
    // 存储结果
    let result = 0
    // 左侧数组
    const leftMax = []
    // 右侧数组
    const rightMax = []
    for (let i = 0; i < height.length; i++) {
        leftMax[i] = max = Math.max(height[i],max)
    }
    // 重置最大值
    max = 0
    for (let j = height.length-1; j >= 0; j--) {
        rightMax[j] = max = Math.max(height[j],max)
    }
    for (let k = 0; k < height.length; k++) {
        result = result + Math.min(leftMax[k], rightMax[k]) - height[k]
    }
    return result
}

可以明显看到,比上述暴力算法代码看起来更为直观,同时也降低了复杂度.

不过还是可以发现,咱们的循环次数有点多,是否可以再次基础上继续优化呢?

ps: 笔者也是只想到了上述解法,观看官方题解和评论区大佬的留言,发现了更为简单的解法

即使用双指针算法,记录左右最大元素,并且使用左右指针计算当前元素左右的最大元素

是不是听起来有点绕, 简单来说(说人话)

 左右指针分别移动时候和给定数组height的源于比较,取出较大的存到leftMax和rightMax中

遍历结束条件是左右指针相遇

其中会有两种情况, 

当左侧最大值小于右侧最大值时候,此时能接到的水量为leftMax-height[left]

当右侧最大值小于左侧最大值时候,此时能接到的水量为rightMax-height[right]

同时需要移动左右指针, 左指针右移,右指针左移

下面附上代码

const trap = height => {
    let result= 0;
    let left = 0;
    let right = height.length -1;
    let leftMax = 0;
    let rightMax = 0;
    while (left < right) {
        // 左侧最大值
        leftMax = Math.max(leftMax, height[left]);
        // 取右侧的最大值
        rightMax = Math.max(rightMax, height[right]);
        if (leftMax < rightMax) {
            // left++左指针右移
            count += leftMax - height[left++];
        } else {
            // right--右指针左移
            count += rightMax - height[right--];
        }
    }
    return result;
}

完美解决问题,只需要遍历一次数组,空间复杂度是O(1),时间复杂度也只需要遍历一侧height,是O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值