力扣42. 接雨水-java多種實現方法

題目

給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

在這裏插入圖片描述
上面是由數組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 感謝 Marcos 貢獻此圖。

示例:

輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6

解法一

暴力法

思路:按列來求水,循環遍歷每列,每一次只看當前列能否裝水,我們只需要關注當前列,以及左邊最高的牆,右邊最高的牆就夠了。
能夠裝下多少水,只需要看左邊最高的牆和右邊最高的牆中較矮的一個。

此處借用某位大佬的圖來分析,原文鏈接:https://leetcode-cn.com/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
當前列能否裝水,有以下三種情況:

1. 較矮的牆高度大於當前所求列的牆的高度
在這裏插入圖片描述
簡化一下,我們把無關列去掉,能夠看的更清楚。
在這裏插入圖片描述
從圖中可以看出,正在求的列與左邊較矮的牆相減即爲當前列的注水量。(注:我們只關注當前所求列與兩邊中最矮的列的高度差)

2. 較矮的牆的高度小於當前所求列的牆的高度
在這裏插入圖片描述
把圖片簡化一下
在這裏插入圖片描述
從圖中可以看出,正在求的列大於最矮的牆,此時注水會從左側流失,故無法裝水。(注:我們只關注當前所求列與兩邊中最矮的列的高度差)

3. 較矮的牆等於當前列的高度
此時和上一種情況一樣,無法裝水
在這裏插入圖片描述
java代碼:

public int trap(int[] height) {
    int sum = 0;
    //最兩端的列不用考慮,因爲一定不會有水。下標從 1 到 length - 2
    //for循環遍歷每一列,每次遍歷都要從新找當前列與左右兩邊的最高列,進行比較,選擇最小的那列與當前列比較,根據三種情況判斷是否可以注水。
    for (int i = 1; i < height.length - 1; i++) {
        int max_left = 0;
        //找出左邊最高列
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > max_left) {
                max_left = height[j];
            }
        }
        int max_right = 0;
        //找出右邊最高列
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > max_right) {
                max_right = height[j];
            }
        }
        //比較兩列中最小的列
        int min = Math.min(max_left, max_right);
        //只有較小的一段大於當前列的高度纔會有水,其他情況不會有水
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

解法二

動態規劃

思路:
對於暴力法中,它找左右牆的高度每次都從新遍歷,時間複雜度極高。在這裏,我們可以用兩個數組採用動態規劃的方式來求。
第一步:
先定義兩個數組,將每列的左右牆的最大高度求出來,存在數組中。
第二步:
在用一次for循環,對所求列進行比較高度即可

java代碼:

public int trap(int[] height) {
    int sum = 0;
    //定義兩個數組存放每列的牆的最大高度
    int[] left_max = new int[height.length];
    int[] right_max = new int[height.length];
    
    //循環左側的每列牆的左側最大高度
    for (int i = 1; i < height.length - 1; i++) {
        left_max[i] = Math.max( left_max[i - 1], height[i - 1]);
    }
    //循環左側的每列牆的左側最大高度
    for (int i = height.length - 1; i >= 0; i--) {
        right_max[i] = Math.max(right_max[i + 1], height[i + 1]);
    }
    //循環所求的每列
    for (int i = 1; i < height.length - 1; i++) {
        int min = Math.min(left_max[i], right_max[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

解法三

雙指針

思路:
和解法 2 相比,我們不從左和從右分開計算,我們想辦法一次完成遍歷。
從動態編程方法的示意圖中我們注意到,只要 right_max[i]>left_max[i] (元素 0 到元素 6),積水高度將由 left_max 決定,類似地left_max[i]>right_max[i](元素 8 到元素 11)。
所以我們可以認爲如果一端有更高的條形塊(例如右端),積水的高度依賴於當前方向的高度(從左到右)。當我們發現另一側(右側)的條形塊高度不是最高的,我們則開始從相反的方向遍歷(從右到左)。
我們必須在遍歷時維護left_max 和 right_max ,但是我們現在可以使用兩個指針交替進行,實現 1 次遍歷即可完成。

此處參考自:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/

算法:

初始化left 指針爲 0 並且 right 指針爲 size-1
While left<right,
do:
If height[left] < height[right]
If height[left]≥left_max, 更新 left_max
Else 累加 left_max−height[left] 到 ans
left = left + 1.
Else
If height[right]≥right_max, 更新right_max
Else 累加 right_max−height[right] 到 ans
right = right - 1.

java代碼

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

此方法的執行效率:

以上爲博主每日一題後的思路彙總,其中參考了不少大佬的精華。如有錯誤之處,歡迎指正.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章