這道題十分有意思,能夠用不少方法作出來,每種方法的思想都值得讓人細細體會。算法
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.app
Solution 1:優化
經過分別計算每一座標i上有多少水,進而將其相加獲得答案。spa
問題是咱們如何知道每一座標i上有多少水呢?仔細思考,其實只有出現「兩高夾一矮」纔可能會存到水,以下圖所示。指針
進而,咱們能夠想到:每一座標i上存多少水是由 1.其自身高度 2.它左邊的最高高度left_most 3.它右邊的最高高度right_most三種因素決定的。code
當 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 存不了水。blog
當min{ left_most,right_most} 大於其自身高度時,這時這三者間出現了「兩高夾一矮」的狀況,故其能存水,並且其存水數= min{left_most,right_most} - height。遞歸
咱們分別來對一些座標進行驗證:leetcode
讀者能夠對每一個座標進行驗證,會發現以上結論皆是正確的。因此,如今咱們的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 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; }
這裏再介紹一種優化方法,雙指針法,在數組首尾分別建立一個指針,兩指針相見時結束循環。
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; }
既然能夠縱向的求存水數,那咱們能不能一層一層的求存水數呢?
這是第一層,當咱們遇到一個空的,且不在邊界,存水數+1,因此第一層咱們在i=2,i=5 時分別+1.
第二層,存水數+4,依次類推,最終能夠求出答案。
代碼筆者就不給了,讀者有興趣的能夠本身寫來試試。
這是在leetcode中solution給出的一種很新穎的解法,利用了棧的結構,經過維護一個非遞增棧來獲得答案。
本質思想仍是利用了要存水必須是「兩高夾一矮」這個特色,只不過這裏是用非遞增棧來實現。
下面定義一些符號以便理解:
stack[-1] 棧頂元素
stack[-2] 棧頂的下面一個元素(即倒數第二個元素)
solution4的整個算法是這麼實現的:遍歷數組,遇到一個元素時,將其與棧頂元素比較,若是其小於等於棧頂元素,直接壓棧,將其放入棧中(爲維護非遞增棧的結構,不能將比棧頂元素大的元素壓棧),
如果其大於棧頂元素,此時必定造成了一個「兩高夾一矮」局面,由於棧是非遞增棧,因此 stack[-1]<stack[-2],又 current>stack[-1],因此是一個「兩高夾一矮」局面,此時算完存水數後棧頂元素出棧,繼續判斷,
遞歸處理便可。
在上例中整個過程是這樣的。
/*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; }