编程语言
首页 > 编程语言> > leetcode42,接雨水,思路清晰,由简入繁讲解,java语言

leetcode42,接雨水,思路清晰,由简入繁讲解,java语言

作者:互联网

记录自己刷题弄的一些思路

先列出问题
给定 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 个单位的雨水(蓝色部分表示雨水)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trapping-rain-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题只要有4个解法,分别是暴力解,动态规划,双指针(面试官比较想看到的答案),以及栈(单调递减栈)。

首先必须会暴力解,很多人直接一上来就想双指针,动态规划等等,只能说除非你做了很多类似的题,一看就能想到的话,不然你很难想出最优解。

这道题先用暴力解解出来,后来才发现暴力解有很多重复操作,因此可以用动态规划来记录一些以前操作的解,这就是一个思路的提升。再到双指针其实是动态规范的一种更好的优化。最后一种解法单调递减栈是比较有意思的解法。

(提示:从暴力解–动态规划–双指针都是垂直方向积水的计算)
1.暴力解
从左到右遍历数组,每一个位置 i 能否积水(或者说积水多少)取决于其左边最大的边界和其右边最大边界来决定的,当左边最大边界及右边最大边界决定后,根据木桶原理,左右边界最小值按理应该是该位置能积水多少,但是别忘了还得减去该位置的高度,因为假如左右边界最小值是2,但是该柱子的高度也是2,就没法积水。

public int trap(int[] height) {
        if(height.length <=2) {
			return 0;
		}
        int left_max;
        int right_max;
        int sum = 0;
		for(int i=1;i<height.length;i++){
            left_max = height[i];
            right_max = height[i];
            for(int j=i-1;j>=0;j--){
                left_max = Math.max(height[j],left_max);
            }

            for(int j=i+1;j<height.length;j++){
                right_max = Math.max(height[j],right_max);
            }

            sum += Math.min(left_max,right_max) - height[i];
        }
        return sum;
    }

时间复杂度O(n^2)
空间复杂度O(1)

2.动态规划
理解了上述暴力解那就很容易地想到,每一步都要去找其左右边界最大值是一个重复过程,例如某个位置 i ,其左边边界最大值无非就是 i-1 位置左边边界的最大值 和 i 处高度的相比,即得出下面状态转移过程(使用动态规划,只要有状态转移方程那就很简单写出相应语言解法)
Left_max[i] = Max( Left_max[i-1] , heigth[i] )

同理右边边界最大值状态转移方程
Right_max[j] = Max( Right_max[j+1] , heigth[j] )

使用两个数组来充当备忘录。

public int trap(int[] height) {
        if(height.length <=2) {
			return 0;
		}
        int size = height.length;
        int[] left_max_arr = new int[size];
        int[] right_max_arr = new int[size];
        int sum = 0;

        left_max_arr[0] = height[0];//初始条件
		for(int i=1;i<size;i++){
            left_max_arr[i] = Math.max(left_max_arr[i-1], height[i]);
        }

        right_max_arr[size-1] = height[size-1];//初始条件
		for(int i=size-2;i>=0;i--){
            right_max_arr[i] = Math.max(right_max_arr[i+1], height[i]);
        }

        for(int i=0;i<size;i++){
           sum += Math.min(left_max_arr[i], right_max_arr[i]) - height[i];
        }

        return sum;
    }

时间复杂度O(n)
空间复杂度O(n)

3.双指针方法(最优解)
从动态规划解法可以看出只要当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定的,反过来当left_max_arr[i] > right_max_arr[i](木桶原理),该 i 位置积水量是由right_max_arr[i]决定的。

因此都不需要两个数组来记录左边最大边界和右边最大边界,只需要两个指针指向数组的left端和right端,同时两个变量记录left_max,和right_max。左右指针移动的规则是当height[left] < height[right],积水量看left_max,并且左指针left往right移动。

或许读者会问,为什么是这样的移动的规则呢?想一想,前面说的当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定。那么当height[left] < height[right],left_max_arr[left] 必然小于right_max_arr[left] ,因为right_max_arr[left] >= right_max_arr[right] >= height[right];

这里比较绕,重新再打一次数学表达式来表示

动态规划解法可以看出当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定的,所以下面的各种思路都是为了得到当height[left] < height[right],left_max_arr[left] <= right_max_arr[left],移动左指针就好;

当height[left] < height[right]时:
left_max_arr[left] = Max(left_max_arr[left-1],height[left]);

而right_max_arr[left] = Max(right_max_arr[left+1],height[left]));
因此left<right时,right_max_arr[left] > right_max_arr[right]
而right_max_arr[right] =Max(right_max_arr[right+1],height[right])) ,并且height[right] > height[left] ;
right_max_arr[left] > right_max_arr[right] >= height[right] > height[left]

因为left,right一开始从左右两端往之间靠,上一次的移动也是采用该规则,就有必然有
left_max_arr[left] <= right_max_arr[left];)

public int trap(int[] height) {
        if(height.length <=2) {
			return 0;
		}
        int left = 0;
        int right = height.length-1;
        int left_max = 0;
        int right_max = 0;
        int sum = 0;
        while(left<right){
            if(height[left] < height[right]){
                left_max = Math.max(left_max,height[left]);
                sum += left_max - height[left];
                left++;
            } else {
                right_max = Math.max(right_max,height[right]);
                sum += right_max - height[right];
                right--;
            }
        }
        return sum;  
    }

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

4.栈思想
上面的思路都是基于垂直的积水,即每个位置 i 的积水 = “某个计算的高度” - 该位置柱子的高度;其实也可以基于横向的计算积水,积水总量 = ∑ (左右边界积水高度极小值 *边界的距离)。这个直接上代码才能说得通

public int trap(int[] height) {
        if(height.length <=2) {
			return 0;
		}
        Stack<Integer> stack = new Stack<Integer>();
        int sum = 0;
        for(int i=0;i<height.length;i++){
            //产生积水的位置是低洼处,也就是此位置的高度比前一个位置高度要大
            while(!stack.isEmpty() && height[stack.peek()] <= height[i]){
                int lastIndex = stack.pop();
                if(!stack.isEmpty()){
                    //横向积水高度
                    int hei = Math.min(height[stack.peek()],height[i]) - height[lastIndex];
                    //横向积水宽度
                    int distance = i - stack.peek() - 1;
                    sum += (distance*hei);
                }      
            }
            stack.push(i);
        }
        return sum; 
    }

标签:arr,right,java,简入,int,max,height,leetcode42,left
来源: https://blog.csdn.net/dogHuaMing/article/details/111932761