LeetCode 42. Trapping Rain Water

天才小鎮刷題家發表於2020-11-27

主要參考思路
全文圍繞一個定理展開:在某個位置 i i i 處,它能存的水,取決於它左右兩邊的最大值中較小的一個

JAVA

① 按行求【超時】

class Solution {
    public int trap(int[] height) {
        int len = height.length;
        int max_height = getMax(height);    // 獲取最高層數
        int ans = 0;                        // 總儲水量
        for (int i = 1; i <= max_height; ++i) {
            int temp = 0;                   // 臨時儲水量
            boolean isStart = false;
            for (int j = 0; j < len; ++j) {
                if (isStart && height[j] < i) {
                    temp++;
                }
                if (height[j] >= i) {
                    ans += temp;
                    temp = 0;
                    isStart = true;
                }
            }
        }
        return ans;
    }

    private int getMax(int[] height) {
        int len = height.length;
        int ans = 0;
        for (int i = 0; i < len; ++i) {
            ans = Math.max(ans, height[i]);
        }
        return ans;
    }
}

但這種程式碼在下面這種情況下會出錯
在這裡插入圖片描述
實際答案是 1 1 1,但因為陣列最右邊多出了 0 0 0,因此實際輸出會變成 2 2 2

② 按列求(動態規劃)【時間複雜度: O ( N ) O(N) O(N) 空間複雜度: O ( N ) O(N) O(N)

class Solution {
    public int trap(int[] height) {
        int len = height.length;
        int[] left_max = new int[len];      // 當前位置往左邊牆最大的高度
        int[] right_max = new int[len];     // 當前位置往右邊牆最大的高度

        for (int i = 1; i <= len - 2; ++i) {
            left_max[i] = Math.max(left_max[i - 1], height[i - 1]);     // left_max[0]自然是 0
        }
        for (int i = len - 2; i >= 1; --i) {
            right_max[i] = Math.max(right_max[i + 1], height[i + 1]);   // right_max[len - 1]自然是 0
        }
        int ans = 0;
        for (int i = 1; i <= len - 2; ++i) {
            int temp_min = Math.min(left_max[i], right_max[i]);
            if (temp_min > height[i]) {
                ans += temp_min - height[i];
            }
        }
        return ans;
    }
}

③ 雙指標【時間複雜度: O ( N ) O(N) O(N) 空間複雜度: O ( 1 ) O(1) O(1)

class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int len = height.length;
        int left_max = 0;
        int right_max = 0;
        int left = 1;
        int right = len - 2;
        for (int i = 1; i <= len - 2; ++i) {
            if (height[left - 1] < height[right + 1]) {
                left_max = Math.max(left_max, height[left - 1]);
                if (height[left] < left_max) {
                    ans += left_max - height[left];
                }
                left++;
            } else {
                right_max = Math.max(right_max, height[right + 1]);
                if (height[right] < right_max) {
                    ans += right_max - height[right];
                }
                right--;
            }
        }
        return ans;
    }
}

為什麼不一開始判斷時用 left_max < right_max

答:因為一開始兩者都等於 0,無法進行判斷,而 height[left - 1] 是可能成為 left_max 的變數, 同理,height [right + 1] 是可能成為 right_max 的變數,只要保證 height[left - 1] < height[right + 1],那麼 left_max 就一定小於 right_max,因此我們可以用 height[left - 1] < height[right + 1] 代替 left_max < right_max

④ 單調棧【時間複雜度: O ( N ) O(N) O(N) 空間複雜度: O ( N ) O(N) O(N)

import java.util.Deque;
import java.util.LinkedList;

public class Solution {
    public int trap(int[] height) {
        if (height == null) {
            return 0;
        }
        Deque<Integer> stack = new LinkedList<>();
        int ans = 0;
        for (int i = 0; i < height.length; i++) {
            while(!stack.isEmpty() && height[stack.peek()] < height[i]) {
                int curIdx = stack.pop();
                // 如果棧頂元素一直相等,那麼全都pop出去,只留第一個。
                while (!stack.isEmpty() && height[stack.peek()] == height[curIdx]) {
                    stack.pop();
                }
                if (!stack.isEmpty()) {
                    int stackTop = stack.peek();
                    // stackTop此時指向的是此次接住的雨水的左邊界的位置。右邊界是當前的柱體,即i。
                    // Math.min(height[stackTop], height[i]) 是左右柱子高度的min,減去height[curIdx]就是雨水的高度。
                    // i - stackTop - 1 是雨水的寬度。
                    ans += (Math.min(height[stackTop], height[i]) - height[curIdx]) * (i - stackTop - 1);
                }
            }
            stack.add(i);
        }
        return ans;
    }
}

單調棧參考文章
雖然但是,我還是看不懂啊!

需要注意的地方是不直接使用 S t a c k Stack Stack,而使用 D e q u e Deque Deque 代替 S t a c k Stack Stack

Python

① 按列求(動態規劃)(48ms)

class Solution:
    def trap(self, height: List[int]) -> int:
        length=len(height)
        ans=0
        left_max=[0]*length					# 相當於初始化陣列
        right_max=[0]*length
        for i in range(1,length-1):
            left_max[i]=max(height[i-1],left_max[i-1])
        for i in range(length-2,0,-1):		# 加個 -1表示倒著遍歷
            right_max[i]=max(height[i+1],right_max[i+1])
        for i in range(1,length-1):
            tmp_max=min(left_max[i],right_max[i])
            if tmp_max>height[i]:
                ans+=tmp_max-height[i]
        return ans

② 雙指標(44ms)

class Solution:
    def trap(self, height: List[int]) -> int:
        length=len(height)
        ans=0
        left_max,right_max=0,0
        left,right=1,length-2
        for i in range(1,length-1):			# 可以替換成 while left<=right:
            if height[left-1]<height[right+1]:
                left_max=max(left_max,height[left-1])
                if left_max>height[left]:
                    ans+=left_max-height[left]
                left+=1
            else:
                right_max=max(right_max,height[right+1])
                if right_max>height[right]:
                    ans+=right_max-height[right]
                right-=1
        return ans

③ 單調棧(44ms)

class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        if n == 0:
            return 0
        stack = []
        ans = 0
        for i in range(n):
            while stack and stack[-1][0] < height[i]:
                tmp = stack.pop()  # 刪除的是最右邊的元素,因此可作為堆使用
                while stack and stack[-1][0] == tmp[0]:
                    stack.pop()
                if stack:
                    ans += (min(stack[-1][0], height[i])-tmp[0])*(i-stack[-1][1]-1)
            stack.append([height[i], i])
        return ans

本程式碼的 s t a c k stack stack 是二維的,一維代表索引,二維的 stack[i][0] 代表牆高度,stack[-1][1] 代表下標

判斷 s t a c k stack stack 是不是空不需要使用函式,只用 if stack 即可

相關文章