【經典】接雨水

一、題目

力扣原題:https://leetcode-cn.com/problems/trapping-rain-water/submissions/

二、暴力

class Solution {
    public int trap(int[] height) {
        if (null == height || 0 == height.length) {
            return 0;
        }

        int result = 0;
        for (int i = 1; i < height.length - 1; i++) {
            // 找到左邊界
            int left = i - 1;
            int leftVal = height[left];
            while (left >= 0) {
                leftVal = Math.max(leftVal, height[left]);
                left--;
            }

            // 找到右邊界
            int right = i + 1;
            int rightVal = height[right];
            while (right < height.length) {
                rightVal = Math.max(rightVal, height[right]);
                right++;
            }

            // 計算當前位置可以存儲多少雨水
            int cur = height[i];
            if (cur < leftVal && cur < rightVal) {
                result = result + (Math.min(leftVal, rightVal) - cur);
            }
        }
        return result;
    }
}
  • 基本思路:遍歷數組,針對每個柱子,往左、往右分別找到最高的邊界,當前柱子能盛水的數量爲左右邊界的小值減去當前柱子的高度。
  • 時間複雜度:O(n^2)。針對每個柱子,需要左右遍歷找到邊界。
  • 空間複雜度:O(1)

三、動態規劃

class Solution {
    public int trap(int[] height) {
        if (null == height || 0 == height.length) {
            return 0;
        }

        // 保存左峯值
        int[] leftMax = new int[height.length];
        leftMax[0] = height[0];
        for (int i = 1; i < height.length; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }

        // 保存右峯值
        int[] rightMax = new int[height.length];
        rightMax[height.length - 1] = height[height.length - 1];
        for (int i = height.length -  2; i >= 0 ;i--) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }

        int result = 0;
        for (int i = 0; i <  height.length; i++) {
            result = result + (Math.min(leftMax[i], rightMax[i]) - height[i]);
        }
        return result;
    }
}
  • 基本思路:觀察暴力解法,可以發現求解的過程中多了很多重複掃描計算左右邊界的運算。可以通過動態規劃將每個柱子的左右峯值邊界保存起來,降低時間複雜度。
  • 時間複雜度:O(n)
  • 空間複雜度:O(n)

四、雙指針

class Solution {
    public int trap(int[] height) {
        if (null == height || 0 == height.length) {
            return 0;
        }

        int result = 0;
        // 記錄左右峯值
        int leftMax = 0;
        int rightMax = 0;
        // 記錄左右指針
        int left = 0;
        int right = height.length - 1;
        while (left <= right) {
            // 若leftMax <= rightMax,說明瓶頸在左邊界
            if (leftMax <= rightMax) {
                result = result + Math.max(0, leftMax - height[left]);
                leftMax = Math.max(leftMax, height[left]);
                left++;
            }
            // 若leftMax > rightMax,說明瓶頸在右邊界
            else {
                result = result + Math.max(0, rightMax - height[right]);
                rightMax = Math.max(rightMax, height[right]);
                right--;
            }
        }
        return result;
    }
}
  • 基本思路:基於動態規劃解法的啓發,可以通過leftMax、rightMax保存左右峯值,並通過雙指針往中間移動。由於我們只關心leftMax、rightMax的小值,因此有以下推論:
    • 當leftMax <= rightMax,當前柱子盛水量的瓶頸在於leftMax;
    • 當leftMax > rightMax,當前柱子盛水量的瓶頸在於rightMax;
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)

五、總結

  • 關鍵在於發現當前柱子的盛水量由其左右峯值的小值決定,盛水量 = min(左峯值,右峯值)- 當前柱子的高度;
  • 通過暴力解法,可以發現存在很多重複的掃描,可以通過動態規劃將每個柱子的左右峯值保存起來,降低時間複雜度;
  • 雙指針是基於動態規劃思路衍生出來的,比較有跳躍性,可以將空間複雜度降至O(1);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章