# 【LeetCode刷題】【演算法學習筆記】相向雙指針 接水桶
### 11. Container With Most Water
✎ 題目連結:[Leetcode](https://leetcode.com/problems/container-with-most-water/description/) / [力扣](https://leetcode.cn/problems/container-with-most-water/description/)
:::spoiler 💡思路
1. reference: [灵茶山艾府](https://leetcode.cn/problems/container-with-most-water/solutions/1974337/by-endlesscheng-f0xz/)
2. 說明: 目標是找出兩條垂直線組成的最大容器,使容器內的水最多。
3. 思路: 我們有一組數字 ==height==,代表不同位置的垂直線長度,每條線的 x 座標對應索引位置,而 y 座標則是 ==height[i]==。我們要找出兩條線,使它們與 x 軸組成的長方形面積最大。
我們這裡使用 雙指針法 **(Two Pointers)**,透過 **「從兩端向內移動」** 的方式,來找到最大的容器。
:::
```python=
class Solution:
def maxArea(self, height: List[int]) -> int: # height 為高度
# 初始化指針
ans = left = 0 # 用來存儲目前找到的最大面積
right = len(height) - 1
while left < right:
# 計算當前面積
area = (right - left) * min(height[left], height[right])
# 判斷當前最大面積
ans = max(ans, area)
# 移動指針
# 誰短就移動誰
# 因為總是移動 較短的那一邊,才有機會找到更大的面積
if height[left] < height[right]:
left += 1
else:
right -= 1
# 指針相遇,沒有區間可以形成容器,迴圈終止
# ans 已經存下了過程中找到的最大面積
return ans
```
### 42. Trapping Rain Water
---
✎ 題目連結:[Leetcode](https://leetcode.com/problems/trapping-rain-water/description/) / [力扣](https://leetcode.cn/problems/trapping-rain-water/description/)
:::spoiler 💡思路
1. reference: [灵茶山艾府](https://leetcode.cn/problems/trapping-rain-water/solutions/1974340/zuo-liao-nbian-huan-bu-hui-yi-ge-shi-pin-ukwm/)
2. 說明: 題目希望我們創建 程式碼目的是 **計算積水量**,也就是找出雨水可以存在哪些地方,並計算總共可以存多少水。目的是解決 接雨水(Trapping Rain Water) 的問題,也就是給定一個高度陣列 ==height==,求出凹槽中最多能接多少水。
3. 思路: 水的存量取決於 左邊最高的柱子 和 右邊最高的柱子 之間的最小值,減去當前柱子的高度。
:::
#### 寫法一: 前後綴分解
```python=
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height) # 柱子數
preMax = [0] * n # preMax 記錄從最左邊數過來最大高度
preMax[0] = height[0]
# 拜訪最大高度
for i in range(1, n):
preMax[i] = max(preMax[i - 1], height[i]) # 確保 pre_max[i] 是前面最大柱子的高度
# 計算每個位置的右邊最大值
sufMax = [0] * n
sufMax[n-1] = height[n-1]
for i in range(n-2, -1, -1):
sufMax[i] = max(sufMax[i + 1], height[i])
# 計算每個位置能接的水量
ans = 0
for h, pre, suf in zip(height, preMax, sufMax): # 透過 zip() 來同時來拜訪三個參數
ans += min(pre, suf) - h
return ans
```
#### 寫法二: 相向雙指針
:::spoiler 💡思路
在一個地形 height 中,如果要存水,則該位置的水量取決於:
* 左邊最高的柱子
* 右邊最高的柱子
* 當前的柱子高度
水量計算公式:
水量 = **min(左邊最高柱子, 右邊最高柱子)−當前柱子高度**
我們使用 雙指針,從兩邊向中間移動,**動態計算水量**。
:::
```python=
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height) # 柱子數
ans = left = preMax = sufMax = 0 # 變數初始化
right = n - 1
# 使用雙指針來計算水量
while left < right:
# 計算當前左右邊最高的柱子高度
preMax = max(preMax, height[left])
sufMax = max(sufMax, height[right])
# 誰小誰先移動 因為最高的柱子無法接水 設為相遇位置
# 水位由兩邊最高柱子相對低決定
if preMax < sufMax: # 左柱較低
ans += preMax - height[left]
left += 1
else:
ans += sufMax - height[right]
right -= 1
return ans
```
#### 寫法三: 相向雙指針
:::spoiler 💡思路
* 這段程式碼的核心概念是「木桶效應」:某個位置能存多少水,取決於它左右兩側的最高柱子中較低的那個。
* 我們用 leftMax 和 rightMax 記錄當前遇到的最高柱子,確保我們能正確計算積水量。
:::
```python=
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
# 排除空柱子
if not height:
return 0
# 初始化變數
left, right = 0, len(height) - 1
leftMax, rightMax = 0, 0
waterTrapped = 0
# 使用雙指針計算水量
while left < right:
# 如果右柱較高
if height[left] < height[right]:
# 較低的柱子 左柱決定水容量 計算加水量
if height[left] >= leftMax: # 當前柱子大於等於左柱最高 不加水並更新左柱最高高度
leftMax = height[left]
else:
waterTrapped += leftMax - height[left]
left += 1
# 左柱較高
else:
# 右柱當前高度 大於等於當前右柱最高
if height[right] >= rightMax: # 更新右柱最高高度
rightMax = height[right]
else:
waterTrapped += rightMax - height[right] # 水漥低於水平面 可以容納水
right -= 1
return waterTrapped
```
:::spoiler **寫法二 v.s. 寫法三 效能比較**
**Code 1 比 Code 2 更快,原因如下:**
### **1. 變數更新次數**
- **Code 1:**
- 只更新 `preMax` 和 `sufMax` **一次**(每次迴圈只更新一次)。
- **Code 2:**
- `leftMax` 和 `rightMax` 在某些情況下會 **被重複賦值**(在 `if` 判斷中會多次賦值)。
### **2. 判斷條件更簡潔**
- **Code 1:**
- 直接比較 `preMax` 和 `sufMax`,避免了 `if height[left] < height[right]` 這種額外的條件判斷。
- **Code 2:**
- 內部有兩層 `if-else`,增加了額外的邏輯判斷,導致運算稍微變慢。
### **3. 指標移動效率**
- **Code 1:**
- 指標 `left` 和 `right` 只根據 `preMax < sufMax` 來移動,簡單且高效。
- **Code 2:**
- `left` 和 `right` 的移動依賴於 `height[left] < height[right]`,內部還有 `if height[left] >= leftMax` 的判斷,導致運算稍微變慢。
### **4. 總體比較**
| **比較項目** | **Code 1** | **Code 2** |
|------------|----------|----------|
| 變數更新次數 | 少 | 多 |
| 邏輯判斷次數 | 少 | 多 |
| 指標移動次數 | 少 | 稍多 |
| 效率 | 更快 | 稍慢 |
### **結論**
- **Code 1** 比 **Code 2** **更快**,因為:
- 變數更新次數較少。
- 判斷邏輯較少,減少 `if-else` 開銷。
- 指標移動規則更簡單,避免不必要的計算。
這使得 **Code 1 在實際執行時會略快於 Code 2** 🚀
:::