【力扣高频题】042.接雨水问题

上一篇我们通过采用 双指针 的方法解决了 经典 容器盛水 问题 ,本文我们接着来学习一道在面试中极大概率会被考到的经典题目:接雨水 问题 。

42. 接雨水

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

示例 1:

输入: height = [0,1,0,2,1,0,1,3,2,1,2,1]

输出: 6

解释: 在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

  • 提示:
  • n == height.length
  • 1 <= n <= 2 * 1 0 4 10^4 104
  • 0 <= height[i] <= 1 0 5 10^5 105

思路分析

边界条件: 如果只有 一个 或 两个 柱子,一定是接不到水的。

我们考虑每个柱子的接水情况,当前列能够接多少水,取决于 当前列的高度 以及 左右两侧的最大高度

上一道题 思想类似:能够装多少水量,取决于两端 较短 的竖线的 长度

因此,大致思路就浮现出来了:

对于下标 i 位置的柱子来说,只需计算出左右两侧最大高度的 最小值(即能够达到的最大高度),与当前列的高度 作差 ,即可知道当前列的接水量。

遍历整个数组,将每个位置的接水量进行累加,即可得到最终的接水量。

注意:

  1. 若当前列的高度大于等于左右两侧最大高度的最小值时,接水量为 0 (要避免负值的出现)
  2. 左右两侧边界处 不会有接水量 ,因此从第二个柱子遍历到倒数第二个柱子即可。

暴力代码

public int trap(int[] height) {
    int water = 0;
    // 左右两侧边界不考虑
    for (int i = 1; i < height.length - 1; i++) {
        int leftMax = 0;
        // 找出左侧的最大值
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > leftMax) {
                leftMax = height[j];
            }
        }
        int rightMax = 0;
        // 找出右侧的最大值
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > rightMax) {
                rightMax = height[j];
            }
        }
        // 左右两侧最大高度的 最小值
        int min = Math.min(leftMax, rightMax);
        // 当大于当前列的高度时才有接水量
        if (min > height[i]) {
            water += min - height[i];
        }
    }
    return water;
}

暴力法分析

在该方法中,对于每个数组元素的求解,都需要 O ( n ) O(n) O(n) 的时间复杂度向左右两端遍历整个数组,得到最大值。因此总的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。空间复杂度为 O ( 1 ) O(1) O(1)

思路优化

接下来我们仍采取 双指针的思想 进行优化。

由于当前位置的接水量取决于左右两侧最大值的较小值。因此,可以设置左右指针进行更新。

  1. 设置 LR 双指针记录当前要计算的是 哪个位置 的接水量。

  2. 设置两个最大值的变量 leftMaxrightMax ,用来记录处于当前位置时,左右两侧的 最大值

最大值哪侧更小,就说明此时可以计算出哪侧的接水量了,并记得更新该侧的最值(看此时的高度是否超过了之前的最值高度),之后往下移动。

直至两个指针相遇后,计算出了总的接水量。

优化代码

public static int trap(int[] arr) {
    if (arr == null || arr.length < 3) {
        return 0;
    }
    int N = arr.length;
    int L = 1;
    int leftMax = arr[0];
    int R = N - 2;
    int rightMax = arr[N - 1];
    int water = 0;
    while (L <= R) {
        if (leftMax <= rightMax) {
            water += Math.max(0, leftMax - arr[L]);
            leftMax = Math.max(leftMax, arr[L++]);
        } else {
            water += Math.max(0, rightMax - arr[R]);
            rightMax = Math.max(rightMax, arr[R--]);
        }
    }
    return water;
}

复杂度分析

左右两个指针的移动次数不超过数组的长度,因此时间复杂度为 O ( n ) O(n) O(n) 。空间复杂度为 O ( 1 ) O(1) O(1)


这道题目主要考察了使用 双指针 优化频繁遍历的思想,是算法题中经常出现的考察形式。小伙伴们一定要牢牢掌握哦~

~ 点赞 ~ 关注 ~ 星标 ~ 不迷路 ~!!!

回复「ACM紫书」获取 ACM 算法书籍 ~
回复「算法导论」获取 算法导论第3版 ~

在看 + 转发

让你的小伙伴们一起来学算法吧!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

强连通子图

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值