1. 程式人生 > 其它 >leetcode42,接雨水,思路清晰,由簡入繁講解,java語言

leetcode42,接雨水,思路清晰,由簡入繁講解,java語言

技術標籤:java一些事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; 
    }