42.接雨水

·题目描述

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

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

·解题思路

————————暴力求解(最直接的想法)——————

利用指针遍历,left和right两个指针left从0到n-2,right 在left右边。当right移动遇见元素大于或者等于原始left元素的时候,说明形成凹陷,可以载雨水。

这个的问题在于,当最初始的left为最大元素,而right到达边界也不能大于或等于原始left元素,但是载该内部有小凹陷可以存在雨水,也就是需要对于边界条件进行处理。

解决办法,引入了有边界寻找的条件判断。当可以寻找到右边界,那么就是简单处理。如果寻找不到右边界,寻找从边界条件处left往后的最大元素(不包括left),将最大元素作为right边界计算凹形面积。再left = right 迭代。

为什么要用最大元素作为边界条件呢?这是以为从右边界往前,最大元素会阻挡凹陷。

·代码java

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int left = 0;
        int area = 0;
        while(left < n - 1){
            if(height[left] < height[left + 1]){
                left ++;
                continue;
            }
            int count = 0;
            int right = left + 1;
            boolean fond = false;
            while(right < n){
                if(height[right] >= height[left]){
                    fond = true;
                    break;
                }
                count += height[right];
                right ++;
            }
            if(fond){
                int H = Math.min(height[left], height[right]);
                int W = right - left - 1;
                area += H * W - count;
                left = right ;
            }
             else {
                 int MaxIndex = left + 1;
                 for(int i = left + 1; i < n; i++) {
                     if (height[i] > height[MaxIndex]) {
                         MaxIndex = i;
                     }
                 }
                 right = MaxIndex;
                 count = 0;
                 for(int j = left + 1; j < right; j++){
                     count += height[j];
                 }
                int H = Math.min(height[left], height[right]);
                int W = right - left - 1;
                area += H * W - count;
                left = right ;
            }

        }
        return area;
    }
}

——————官方的动态规划——————

动态规划的原理在于:从左往右将小元素置为左边最大的元素;再从右往左,将小元素置为右边最大的元素。两次扫描的重叠区域再减去原有的数组就是可以盛水的区域。

·代码:

    public int trap(int[] height) {
       int n = height.length;
       int area = 0;
       int[] leftMax = new int[n], rightMax = new int[n];
       leftMax[0] = height[0];
       rightMax[n - 1] = height[n - 1];

       for(int i = 1; i < n ; i ++){
           leftMax[i] = Math.max(leftMax[i - 1], height[i]);
       }
       for(int i = n - 2 ; i >= 0; i --){
           rightMax[i] = Math.max(rightMax[i + 1], height[i]);
       }
       for(int i = 0 ; i < n ; i ++){
           area += Math.min(leftMax[i], rightMax[i]) - height[i];
       }
       return area;
    }

————————将两个数组该为双指针问题——————

从上述动态规划的算法中可以发现,最后的area叠加

area += Math.min(leftMax[i], rightMax[i]) - height[i];

那么可以利用双指针将min计算步骤改为

(height[left] < height[right])

所以最后代码:

    public int trap(int[] height) {
        int n = height.length;
        int area = 0;
        int left =0, right = n - 1;
        int leftMax = 0, rightMax = 0;

        while (left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            if (height[left] < height[right]) {
                area += leftMax - height[left];
                left++;
            } else {
                area += rightMax - height[right];
                right--;
            }
        }
        return area;
    }

---------单调栈---------

使用单调栈解决该问题,每次寻找大于左边界的有边界。其中凹下去的面积就是可以承接雨水的面积,在这其中关键的问题在于,存在大凹陷和小凹陷的时候,如何正确的计算面积。

关键点:

单调栈:只有元素小于栈顶元素的时候,元素入栈;当元素大于栈顶元素的时候,构成凹陷,元素出栈;由于是获取元素的右边更大元素,因此使用单调减栈。

计算面积:首先处理大小凹陷并存的情况(例如:3,2,0,2,3),存在两个可以存雨水的地方。按照栈的处理顺序应该是闲处理(2,0,2)这个凹陷,只要在处理凹陷后保持大凹陷为(3,2,3)表示,大凹陷有一个高度为2的底面。

W= i - left - 1      left表示左端位置

H= Math.min(heigh[i], height[left]) - height[top]      height[top]表示底面

import java.util.Stack;

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        Stack<Integer> index = new Stack<Integer>();
        int ans = 0;

        for(int i = 0; i < n; i++){
            if(index.isEmpty() || height[i] < height[index.peek()]) index.push(i);
            while(!index.isEmpty() && height[index.peek()] < height[i]){
                int top = index.pop();
                if(index.isEmpty()) break;

                int left = index.peek();
                int W = i - left - 1;
                int H = Math.min(height[i], height[left]) - height[top];
                ans += W * H;
            }
            index.push(i);
        }
        return ans;
    }
}
public class Main {
    public static void main(String[] args) {
        Solution solution = new Solution();
        System.out.println(solution.trap(new int[]{4,2,0,3,2,5}));
    }
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值