【刷题1】LeetCode 42. 接雨水 java题解

建议全文背诵

题目

https://leetcode-cn.com/problems/trapping-rain-water/
在这里插入图片描述

方法一:暴力

分析

对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。

复杂度

时间复杂度:O(n²)
空间复杂度:O(1)

代码

class Solution {
    public int trap(int[] height) {
        if(height==null)
            return 0;
        int len=height.length;
        if(len==0)
            return 0;
        int left=0,right=0;
        int res=0;
        for(int i=1;i<len-1;i++){
            left=0;
            right=0;
            for(int j=i;j>=0;j--){
                left=Math.max(left,height[j]);
            }
            for(int j=i;j<len;j++){
                right=Math.max(right,height[j]);
            }
            res+=Math.min(left,right)-height[i];
        }
        return res;
    }
}

结果

在这里插入图片描述

方法二:动态规划

分析

在暴力方法中,我们仅仅为了找到最大值每次都要向左和向右扫描一次。但是我们可以提前存储这个值。因此,可以通过动态编程解决。

找到数组中从下标 i 到最左端最高的条形块高度 left_max。
找到数组中从下标 i 到最右端最高的条形块高度 right_max。
扫描数组 height 并更新答案:
累加res+=Math.min(left_max[i],right_max[i])-height[i],得出结果。

复杂度

时间复杂度:O(n)。
存储最大高度数组,需要两次遍历,每次 O(n) 。
最终使用存储的数据更新ans ,O(n)。
空间复杂度:O(n) 额外空间。
和方法 1 相比使用了额外的 O(n)空间用来放置left_max 和 right_max 数组。

代码

class Solution {
    public int trap(int[] height) {
        if(height==null)
            return 0;
        int len=height.length;
        if(len==0)
            return 0;
        int res=0;
        int[] left_max=new int[len];
        int[] right_max=new int[len];
        left_max[0]=height[0];
        //找到i左边的最大值
        for(int i=1;i<len;i++){
            left_max[i]=Math.max(height[i],left_max[i-1]);
        }
        right_max[len-1]=height[len-1];
        //找到i右边的最大值
        for(int i=len-2;i>=0;i--){
            right_max[i]=Math.max(height[i],right_max[i+1]);
        }
        //计算结果
        for(int i=1;i<len-1;i++){
            res+=Math.min(left_max[i],right_max[i])-height[i];
        }
        return res;
    }
}

结果

在这里插入图片描述

方法三:栈

分析

跟动态规划/双指针的不同是,这里是按行求,而非按列求。
栈内x、栈顶y、当前z,有height[z]>height[y],height[x]>height[y],那么长度为z-x-1,高度为min(height[x],height[z])-height[y]。

维护一个单调递减栈。
我们在遍历数组时维护一个栈。如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案 。

使用栈来存储条形块的索引下标。 遍历数组:
1.当栈非空且 height[current]>height[st.top()] 意味着栈中元素可以被弹出。弹出栈顶元素 top。 计算当前元素和栈顶元素的距离,准备进行填充操作 distance=current−st.top()−1 找出界定高度
bounded_height=min(height[current],height[st.top()])−height[top]
往答案中累加积水量 res+=distance×bounded_height
2.将当前索引下标入栈
3.将 current 移动到下个位置

复杂度

时间复杂度:O(n)。
单次遍历 O(n) ,每个条形块最多访问两次(由于栈的弹入和弹出),并且弹入和弹出栈都是 O(1) 的。
空间复杂度:O(n)。 栈最多在阶梯型或平坦型条形块结构中占用 O(n) 的空间。

代码

class Solution {
    public int trap(int[] height) {
        if(height==null||height.length==0)
            return 0;

        int len=height.length;
        Deque<Integer> stack=new LinkedList<>();
        int current=0;
        int res=0;
        while(current<len){
            while(!stack.isEmpty()&&height[stack.peekLast()]<height[current]){
                int top=stack.removeLast();
                if(stack.isEmpty())
                    break;
                int before=stack.peekLast();//top在栈中的前一个
                int width=current-before-1;//before和current之间的就是可以接的,宽度
                int h=Math.min(height[before],height[current])-height[top];//高度
                res+=width*h;
            }
            //循环结束时,栈里没有大于当前数的了,将当前数入栈满足栈顶最小。
            stack.addLast(current);
            current++;//遍历下一个数
        }
        return res;
    }
}

结果

在这里插入图片描述

方法四:双指针

分析

所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
我们必须在遍历时维护left_max 和right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。
在这里插入图片描述

复杂度

时间复杂度:O(n)。单次遍历的时间O(n)。
空间复杂度:O(1) 的额外空间。left,right, left_max 和 right_max 只需要常数的空间。

代码

class Solution {
    public int trap(int[] height) {
        int n=height.length;
        if(n<3) return 0;//不能装水
        int left_max=0;//左边最高下标
        int right_max=n-1;//右边最高下标
        int left=0;//左边
        int right=n-1;//右边
        int sum=0;
        while(left<right){
        	//height[left]<height[right]<=height[right_max],取决于左边
            if(height[left]<height[right]){
                if(height[left_max]>height[left]){//左边最大值大于left
                    sum+=height[left_max]-height[left];
                }
                else{//左边最大值小于等于left,不能装水
                    left_max=left;
                }
                left++;
            }
            else{
                if(height[right_max]>height[right]){
                    sum+=height[right_max]-height[right];
                }
                else{
                    right_max=right;
                }
                right--;
            }
        }
        return sum;
    }
}
class Solution {
    public int trap(int[] height) {
        if(height==null)
            return 0;
        int len=height.length;
        if(len==0)
            return 0;
        int res=0;
        int left=0;
        int right=len-1;
        int left_max=0;
        int right_max=len-1;
        while(left<right){
            if(height[left]<height[right]){
                if(height[left]>height[left_max])
                    left_max=left;
                else
                    res+=height[left_max]-height[left];
                left++;
            }
            else{
                if(height[right]>height[right_max])
                    right_max=right;
                else
                    res+=height[right_max]-height[right];
                right--;
            }
        }
        return res;
    }
}

结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值