牛客Top200---接雨水问题(java详解)

题目

在这里插入图片描述
在这里插入图片描述

分析一(暴力法)

首先暴力破解是最容易想到,且最容易理解的
思路,我们观察可得,第一列和最后一列是不可能接到水的,所以只要看中间即可,我们只要从下标1遍历到arr.length-1,然后获取中间每一列的接水量求和即可,具体怎么求呢?
1、首先获取第i根柱子,左右两边最高的柱子,比如i=1,那么其左边最高的就是arr[0]=3,右边最高就是arr[3]=5,然后取这两根中较短的一根减去柱子i的高度即可,故可得公式Math.min(max_left,max_right)-arr[i],比如i=1,该公式就是arr[0]-arr[1]=3-1=2,故i=1位置上接水量就是2,然后一个个遍历求和即可

核心:当前柱子盛水的多少取决于左右柱子的较小值减去当前元素柱子高度
在这里插入图片描述

时间复杂度:O(n2),这个暴力法对于常规数组还行,但是一旦数组长度过长,明显慢了许多,所以需要优化

import java.util.*;

public class Solution {
    /**
     * max water
     * @param arr int整型一维数组 the array
     * @return long长整型
     */
    public long maxWater (int[] arr) {
        // write code here
        //暴力解法
        //思路:对于数组每一个元素,我们需要找到它左边的最高柱子以及它右边的最高柱子
        //而盛水的多少取决于左右柱子的较小值减去当前元素柱子高度
        //即求每一条能接多少,然后求和
        long res=0;//接的雨水量
        int len = arr.length;
        if(len <= 2){
            return res;//至少长度要为3才能接水
        }
        //遍历数组,第一个和最后一个是接不了水的,所以不用遍历
        for(int i = 1 ; i < len - 1 ; i++){
            int max_left = 0,max_right = 0;
            //找i左边最高的柱子
            for(int j = i ; j >= 0 ; j--){
                max_left = Math.max(max_left,arr[j]);
            }
            //找i右边最高的柱子
            for(int j = i ; j <= len - 1 ; j++){
                max_right = Math.max(max_right,arr[j]);
            }
            //每次加上i柱子上接的水,即取i左右最高柱子较小柱子高度-arr[i]就等于i处接水量
            res += (Math.min(max_left,max_right) - arr[i]);
        }
        return res;
    }
}

上面代码没错,但是还可以得到优化,因为复杂度比较高,我们这里直接上时间复杂度为O(n)的双指针法,理解上面解法,可以进一步让我们理解这个双指针法

分析二(双指针法)

双指针法就是用left,right分别指向两端,然后两个指针分别向中间移动,上面只有i遍历,这次left,right都会移动,只遍历一次,直到left > right,则表示遍历完毕,也即所有柱子都遍历了。max_left记录left位置左边最高的柱子,max_right记录right位置右边最高的柱子,核心:当前柱子盛水的多少取决于左右柱子的较小值减去当前元素柱子高度,核心不变
分两种情况:
(1) max_left < max_right (取max_left)
求和,加上max_left-arr[left](接水量),但这里max_left初始为0,max_left-arr[left]<0,所以 Math.max(0, max_left-arr[left])保证>=0,然后更新max_left,因为要保证每次得到left位置左边最大,最后left右移一位
(2) max_left > max_right(取max_right)
同理(1),求和加上接水量,再更新max_right,最后right左移一位

时间复杂度为 O(n)

代码如下:

import java.util.*;

public class Solution {
    public long maxWater (int[] arr) {
        long res = 0;
        int len = arr.length;
        // 特判
        if (len < 3) return res;
        // 双指针法
        int left = 0, right = arr.length - 1;
        long max_left = 0, max_right = 0;//记录左右最大柱子
        while (left <= right) {
        	//左边最大柱子小
            if (max_left < max_right) {
                res += Math.max(0, max_left-arr[left]);//求和,max_left-arr[left]初始相减为0
                max_left = Math.max(max_left, arr[left]);//若arr[left]更大则更新为最大
                left++;//left自增,往右移动一次
            } else {//右边最大柱子小
                res += Math.max(0, max_right-arr[right]);//求和,结果必须>0,才算有雨水
                max_right = Math.max(max_right, arr[right]);//若arr[right]更大则更新为最大
                right--;//right自减,往左移动一次
            }
        }
        return res;
    }
}

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小样x

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

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

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

打赏作者

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

抵扣说明:

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

余额充值