LeetCode42.接雨水(暴力、DP、單調棧)
阿新 • • 發佈:2021-01-08
技術標籤:演算法資料結構leetcode演算法資料結構動態規劃c++
42. 接雨水
題目描述:
給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。
示例1.
輸入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
輸出:6
解釋:上面是由陣列 [ 0 , 1 , 0 , 2 , 1 , 0 , 1 , 3 , 2 , 1 , 2 , 1 ] [0,1,0,2,1,0,1,3,2,1,2,1] [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 6 6個單位的雨水(藍色部分表示雨水);
示例 2.
輸入:height = [4,2,0,3,2,5]
輸出:9
思路:
- 暴力方法:按照列的方式對每列所能接到的雨水量進行計算,對於每列而言,找到其左右兩邊的最高的柱子,取左右最高柱子中的小值,減去該列的柱子高度即是該列柱子所能接水量。對於每個柱子而言,尋找其左右最高柱子需要額外花費 O ( n ) O(n) O(n)時間,對於 n n n個柱子而言,總體的時間複雜度為 O ( n 2 ) O(n^2) O(n2),而其中未使用輔助空間,則空間複雜度為 O ( n ) O(n) O(n)。
int trap(vector<int>& height) {
//按照列計算的暴力方法,時間複雜度O(n^2),空間複雜度O(1)
//對於每列而言,找到其左右兩邊的最高的柱子,取左右最高柱子中的小值,減去該列的柱子高度
//即為該列的接水多少
int res = 0;
for(int i = 0; i < height.size(); ++i) {
if(i == 0 || i == height.size()-1) continue;
int rHeight = height[i], lHeight = height[i];
for(int rIdx = i+1; rIdx < height. size(); ++rIdx) //尋找右邊最高的柱子
if(height[rIdx] > rHeight) rHeight = height[rIdx];
for(int lIdx = i-1; lIdx >= 0; --lIdx) //尋找左邊最高的柱子
if(height[lIdx] > lHeight) lHeight = height[lIdx];
res += max(min(lHeight, rHeight) - height[i], 0); //若是接水量大於0,加上該水量
}
return res;
}
- 動態規劃方法:對於暴力方法,可以看到每次尋找當前柱子的左右最高柱子的過程中有許多的重複操作,則可開闢兩個相同大小的輔助空間 m a x L e f t maxLeft maxLeft, m a x R i g h t maxRight maxRight分別記錄每個柱子 h e i g h t [ i ] height[i] height[i]的左最高柱子 m a x L e f t [ i ] maxLeft[i] maxLeft[i]和右最高柱子高度 m a x R i g h t [ i ] maxRight[i] maxRight[i]。對於左最高柱子,從左向右進行記錄,有 m a x L e f t [ i ] = m a x ( h e i g h t [ i ] , m a x L e f t [ i − 1 ] ) maxLeft[i]=max(height[i], maxLeft[i-1]) maxLeft[i]=max(height[i],maxLeft[i−1]);對於右最高柱子,從右向左進行記錄,有 m a x R i g h t [ i ] = m a x ( h e i g h t [ i ] , m a x R i g h t [ i − 1 ] ) maxRight[i]=max(height[i], maxRight[i-1]) maxRight[i]=max(height[i],maxRight[i−1])。有了左右最高柱的高度值,計算總接水量和暴力方法類同。
int trap(vector<int>& height) {
if(height.size() < 2) return 0;
//dp演算法
//從左向右遍歷:maxLeft[i] = max(height[i], maxLeft[i - 1]);
//從右向左遍歷:maxRight[i] = max(height[i], maxRight[i + 1]);
int res = 0;
vector<int> maxLeft(height.size(), 0);
vector<int> maxRight(height.size(), 0);
// 記錄每個柱子左邊柱子最大高度
maxLeft[0] = height[0];
for(int i = 1; i < maxLeft.size(); ++i)
maxLeft[i] = max(height[i], maxLeft[i-1]);
// 記錄每個柱子右邊柱子最大高度
maxRight[maxRight.size()-1] = height[height.size()-1];
for(int i = maxRight.size()-2; i >= 0; --i)
maxRight[i] = max(height[i], maxRight[i+1]);
for(int i = 0; i < height.size(); ++i)
res += max(min(maxLeft[i], maxRight[i]) - height[i], 0);
return res;
}
- 單調棧 (類似題目):首先其是按行計算的。如下圖所示:
- 使用的單調遞減棧,棧頂至棧底是單調遞增的,即棧底到棧頂是從大到小的。
- 若是當前柱子高度小於棧頂柱子高度,則將當前柱子的列數壓入棧中;
- 若是當前柱子高度等於棧頂柱子高度,則將棧頂柱子彈出,將當前柱子壓入;
- 若是當前柱子高度大於棧頂柱子高度,則出現凹槽,依次與棧頂元素進行對照,計算凹槽的面積並累加,直至當前柱高不大於棧頂柱高,或是棧為空,再將當前柱子壓入。
- 按照如上方法,直至將所有的柱子遍歷一遍即可。
int trap(vector<int>& height) {
//使用單調棧方法,棧頂至棧底是單調遞增的,即棧底到棧頂是從大到小的
//若是當前柱子高度小於棧頂柱子高度,則將當前柱子的列數壓入棧中;
//若是當前柱子高度等於棧頂柱子高度,則將棧頂柱子彈出,將當前柱子壓入;
//若是當前柱子高度大於棧頂柱子高度,則出現凹槽,依次與棧頂元素進行對照,計算凹槽的面積並累加,
//直至當前柱高不大於棧頂柱高,或是棧為空,再將當前柱子壓入
stack<int> idxRrd;
idxRrd.push(0);
int res = 0;
for(int i = 1; i < height.size(); ++i) {
if(height[i] < height[idxRrd.top()]) { //當前柱子高度小於棧頂所對應的高度
idxRrd.push(i);
} else if(height[i] == height[idxRrd.top()]) { //當前柱子高度等於棧頂所對應的高度
idxRrd.pop();
idxRrd.push(i);
} else { //當前柱子高度大於棧頂所對應的高度,此時出現凹槽
while(!idxRrd.empty() && height[i] > height[idxRrd.top()]) {
int caveIdx = idxRrd.top();
idxRrd.pop();
if(!idxRrd.empty()) {
int h = min(height[idxRrd.top()], height[i]) - height[caveIdx];
int w = i - idxRrd.top() - 1;
res += h*w;
}
}
idxRrd.push(i);
}
}
return res;
}