leetcode#42 Trapping rain water
這道題十分有意思,可以用很多方法做出來,每種方法的思想都值得讓人細細體會。
42. Trapping Rain Water
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
Solution 1:
通過分別計算每一坐標i上有多少水,進而將其相加得到答案。
問題是我們如何知道每一坐標i上有多少水呢?仔細思考,其實只有出現“兩高夾一矮”才可能會存到水,如下圖所示。
進而,我們可以想到:每一坐標i上存多少水是由 1.其自身高度 2.它左邊的最高高度left_most 3.它右邊的最高高度right_most三種因素決定的。
當 min{ left_most, right_most} 小於或等於其自身高度時,它能存的水就是0,比如array[1]=1,其left_most= array[0]=0, 其right_most=array[7]=3, min{left_most, right_most}=left_most=0< height= array[1]=1,這也就是說坐標1 存不了水。
當min{ left_most,right_most} 大於其自身高度時,這時這三者間出現了“兩高夾一矮”的情況,故其能存水,而且其存水數= min{left_most,right_most} - height。
我們分別來對一些坐標進行驗證:
坐標1,存水數=0.//正確
坐標2,leff_most=1,right_most=3,存水數=left_most-height=1-0=1.//正確
坐標3,left_most=1,right_most=3,min{left_most,right_most}=1=height,存水數=0.//正確
讀者可以對每個坐標進行驗證,會發現以上結論皆是正確的。所以,現在我們的solution就出來了,我們只需要求出每個坐標對應的left_most和right_most,再把存水數相加,就是總的存水數了。
所以,很朴素自然的一個想法就是,遍歷一遍數組,對每個數組元素遍歷左邊一次求出left_most,遍歷右邊一次求出right_most。
代碼如下,
//29ms 6.36%
//complexity: O(N^2)
int trap(vector<int>& height)
{
int ans = 0;
int size = height.size();
for (int i = 1; i < size - 1; i++) {
int max_left = 0, max_right = 0;
for (int j = i; j >= 0; j--) { //Search the left part for max bar size
max_left = max(max_left, height[j]);
}
for (int j = i; j < size; j++) { //Search the right part for max bar size
max_right = max(max_right, height[j]);
}
ans += min(max_left, max_right) - height[i];
}
return ans;
}
Solution 2:
在solution 1里,我們已經知道只要求出left_most和right_most,就可以求出答案,那能不能優化一下求這兩個數的過程呢?當然是可以的,我們只需要左遍歷一次數組,右遍歷一次數組,即可得到left_most和right_most。
/*Solution2: 上一種方法其實有優化的空間 通過兩次for循環可分別求得left_most和right_most,第三次for循環即可求得sum, complexity: O(n) */ int trap(vector<int>& height) { if(height == null) return 0; int ans = 0; int size = height.size(); vector<int> left_max(size), right_max(size); left_max[0] = height[0]; for (int i = 1; i < size; i++) { left_max[i] = max(height[i], left_max[i - 1]); } right_max[size - 1] = height[size - 1]; for (int i = size - 2; i >= 0; i--) { right_max[i] = max(height[i], right_max[i + 1]); } for (int i = 1; i < size - 1; i++) { ans += min(left_max[i], right_max[i]) - height[i]; } return ans; }
Solution 3:
這里再介紹一種優化方法,雙指針法,在數組首尾分別創建一個指針,兩指針相見時結束循環。
int trap(vector<int>& height) { int left = 0, right = height.size() - 1; int ans = 0; int left_max = 0, right_max = 0; while (left < right) { if (height[left] < height[right]) { height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]); ++left; } else { height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]); --right; } } return ans; }
Solution 4:
既然可以縱向的求存水數,那我們能不能一層一層的求存水數呢?
這是第一層,當我們遇到一個空的,且不在邊界,存水數+1,所以第一層我們在i=2,i=5 時分別+1.
第二層,存水數+4,依次類推,最終可以求出答案。
代碼筆者就不給了,讀者有興趣的可以自己寫來試試。
Soluton 5:
這是在leetcode中solution給出的一種很新穎的解法,利用了棧的結構,通過維護一個非遞增棧來得到答案。
本質思想還是利用了要存水必須是“兩高夾一矮”這個特點,只不過這里是用非遞增棧來實現。
下面定義一些符號以便理解:
stack[-1] 棧頂元素
stack[-2] 棧頂的下面一個元素(即倒數第二個元素)
solution4的整個算法是這么實現的:遍歷數組,遇到一個元素時,將其與棧頂元素比較,如果其小於等於棧頂元素,直接壓棧,將其放入棧中(為維護非遞增棧的結構,不能將比棧頂元素大的元素壓棧),
若是其大於棧頂元素,此時一定形成了一個“兩高夾一矮”局面,因為棧是非遞增棧,所以 stack[-1]<stack[-2],又 current>stack[-1],所以是一個“兩高夾一矮”局面,此時算完存水數后棧頂元素出棧,繼續判斷,
遞歸處理即可。
在上例中整個過程是這樣的。
step0: 0不入棧
step1: 1>0 array[1] 入棧 棧:[1]
step2: 0<stack[-1]=1 入棧 棧:[1,0]
step3: 2>stack[-1]=0 存水數+1,0出棧,2>stack[-1]=1, 此時stack內元素不足2,不足以形成“兩高夾一矮”局面, 1出棧,2入棧 棧:[2]
step4: 1<stack[-1]=2 1入棧 棧:[2,1]
step5: 0<stack[-1]=1 0入棧 棧:[2,1,0]
step6: 1>stack[-1]=0 存水數+1,0出棧 1=stack[-1] 1入棧 棧:[2,1,1]
step7: 3>stack[-1]=1 存水數+0,1出棧 3>stack[-1]=1 存水數+3,1出棧 3>stack[-1]=2 存水數+0 2出棧 3入棧 棧:[3]
step8: 2<stack[-1] 2入棧 棧:[3,2]
step9: 1<stack[-1] 1入棧 棧:[3,2,1]
step10: 2>stack[-1] 存水數+1 1出棧 2入棧 棧:[3,2,2]
step 11:1<stack[-1] 入棧 棧:[3,2,2,1]
done
/*Solution4 Stack solution 這個solution利用了棧結構,通過維護一個非遞增棧,一步一步算出ans */ int trap(vector<int>& height) { int ans = 0, current = 0; stack<int> st; while (current < height.size()) { while (!st.empty() && height[current] > height[st.top()]) { int top = st.top(); st.pop(); if (st.empty()) break; int distance = current - st.top() - 1; int bounded_height = min(height[current], height[st.top()]) - height[top]; ans += distance * bounded_height; } st.push(current++); } return ans; }