接雨水解法詳解:
題目:
基本思路:從圖上可以看出要想接住雨水,必須是凹字形的,也就是當前位置的左右兩邊必須存在高度大於它的地方,所以我們要想知道當前位置最多能存儲多少水,只需找到左邊最高處max_left
和右邊最高處max_right
,取他們兩個較小的那邊計算即可(短板效應)。
其實接下來的解法要解決的問題就是如何找到max_right
和max_left
。
不過我們首先來看一個無法AC的解法:
解法一:按行
按行顧名思義就是一行一行地進行計算,首先我們計算第一行,設置一個變量temp臨時存儲當前接的水和開始標志isStart,當碰到第一個高度大於等於行數的位置時,temp置0,開始計算,接下來如果碰到小於行數的位置時,temp+1
,碰到大於行數的位置時,temp置0,繼續往后遍歷,如此遍歷完一行繼續遍歷下一行。
class Solution {
public int trap(int[] height) {
int sum = 0;
int max = getMax(height);//找到最大的高度,以便遍歷。
for (int i = 1; i <= max; i++) {
boolean isStart = false; //標記是否開始更新 temp
int temp_sum = 0;
for (int j = 0; j < height.length; j++) {
if (isStart && height[j] < i) {
temp_sum++;
}
if (height[j] >= i) {
sum = sum + temp_sum;
temp_sum = 0;
isStart = true;
}
}
}
return sum;
}
private int getMax(int[] height) {
int max = 0;
for (int i = 0; i < height.length; i++) {
if (height[i] > max) {
max = height[i];
}
}
return max;
}
}
不過此解法會在最后兩個測試用例處,TLE掉,我們只需了解一下這種思想,medium難度的題還是可以過的。
解法二:按列
這個解法就是我們剛開始所說思路的最朴素的解法了,要求i
位置接水量,只需找到i
左邊最高位置的高度max_left
和右邊最高位置的高度max_right
,然后取較小的那個min_height=min(max_right,max_left)
,最后計算i
位置接水量:min_height-height[i]
,當然如果min_height<height[i]
,就不用計算啦。
class Solution {
public int trap(int[] height) {
int ans=0;
for(int i=1;i<height.length-1;i++){//第一個和最后一個位置肯定存不了水
int max_left=0;
for(int j=i-1;j>=0;j--){//找到當前位置左邊最高處
max_left=Math.max(max_left,height[j]);
}
int max_right=0;
for(int j=i+1;j<height.length;j++)//找到當前位置右邊最高處
max_right=Math.max(max_right,height[j]);
int min_high=Math.min(max_right,max_left);
if(min_high>height[i]){
ans+=min_high-height[i];
}
}
return ans;
}
}
解法三:動態規划
我們知道解法二每到一個位置都會遍歷左右兩邊來尋找它的左右最高處,這樣就會導致O(n^2)的時間復雜度,我們能不能事先就找好每個位置對應的max_left
和max_right
,顯而易見是可以的,所以我們需要聲明兩個數組max_left
和max_right
來存儲每個位置對應的左右最高點,因為我們只在它左右兩邊尋找,我們可以寫出當前位置i
的max_left[i]=max(max_left[i-1],height[i-1])
,右邊同理,接下來就是和解法二一樣了。
class Solution {
public int trap(int[] height) {
int[] max_left=new int[height.length];
int[] max_right=new int[height.length];
int ans=0;
for(int i=1;i<height.length-1;i++){
max_left[i]=Math.max(max_left[i-1],height[i-1]);
}
for(int i=height.length-2;i>=1;i--){
max_right[i]=Math.max(max_right[i+1],height[i+1]);
}
for(int i=1;i<height.length-1;i++){
int min_height=Math.min(max_right[i],max_left[i]);
if(min_height>height[i]){
ans+=min_height-height[i];
}
}
return ans;
}
}
解法四:雙指針
從動態規划解法可以看出,只要max_left[i]<max_right[i]
,積水的高度由max_left[i]
決定,也就是積水的高度是由較低的那邊決定,所以此時我們應該繼續由較低->較高那個方向遍歷,直到此時較低的這邊發現比另一邊更高的位置,再轉換方向,這樣我們就可以一次遍歷且只需兩個指針便可完成計算。
- 初始化 left 指針為 0 並且 right 指針為 size-1
While left<right, do:
If height[left] < height[right]
If height[left]≥left_max, 更新 left_max
Else left_max−height[left] 到ans
left = left + 1.
Else
If height[right]≥right_max, 更新 right_max
Else 累加 right_max−height[right] 到 ans
right = right - 1.
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;
}
解法五:單調棧
思路:單調棧可以保證棧底到棧頂是單調遞減的,也就是說棧頂元素可以由它前一個元素所界定,我們遍歷數組時可以維護一個單調棧來存儲索引,當當前元素小於棧頂元素時,入棧,當當前元素大於等於棧頂元素時,此時棧頂元素
的接水量就取決於其前一個元素
和當前元素
較小的那個,棧頂元素出棧。
算法:
- 使用棧來存儲條形塊的索引下標。
- 遍歷數組:
- 當棧非空且 height[current]>height[st.top()]
- 意味着棧中元素可以被彈出。彈出棧頂元素
- 計算當前元素和棧頂元素的距離,准備進行填充操作
distance=current−st.top()−1
- 找出界定高度
- bounded_height=min(height[current],height[st.top()])−height[top]
- 往答案中累加積水量ans+=distance×bounded_height
- 將當前索引下標入棧
- 將 current 移動到下個位置
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;
}
總結:
這種多解法的題目可以盡量去了解它的每一種解法,這對擴展自己的解題思維有很大的幫助,其實這個題目這么多解法大部分都是在解決怎么高效找出左右兩邊最高點,時間復雜度由O(n^2)提升到O(n),空間復雜度也由O(n)->O(1),所以有時候很多方法都是由暴力逐漸改善的,本人表達可能詞不達意,還請諒解,如有任何問題,請留言指出,不甚感激。
參考出處