一、題目
力扣原題: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);