詳解一道高頻面試題:接雨水

  • 2019 年 10 月 8 日
  • 筆記

來源:labuladong

作者:labuladong

接雨水這道題目挺有意思,在面試題中出現頻率還挺高的,本文就來步步優化,講解一下這道題。

這道題目實際上出自 LeetCode:

題目來自 leetcode-cn.com

就是用一個數組表示一個條形圖,問你這個條形圖最多能接多少水。

int trap(int[] height);

下面就來由淺入深介紹暴力解法 -> 備忘錄解法 -> 雙指針解法,在 O(N) 時間 O(1) 空間內解決這個問題。

一、核心思路

我第一次看到這個問題,無計可施,完全沒有思路,相信很多朋友跟我一樣。所以對於這種問題,我們不要想整體,而應該去想局部;就像之前的文章處理字元串問題,不要考慮如何處理整個字元串,而是去思考應該如何處理每一個字元。

這麼一想,可以發現這道題的思路其實很簡單。具體來說,僅僅對於位置 i,能裝下多少水呢?

能裝 2 格水。為什麼恰好是兩格水呢?因為 height[i] 的高度為 0,而這裡最多能盛 2 格水,2-0=2。

為什麼位置 i 最多能盛 2 格水呢?因為,位置 i 能達到的水柱高度和其左邊的最高柱子、右邊的最高柱子有關,我們分別稱這兩個柱子高度為l_max和r_max;位置 i 最大的水柱高度就是min(l_max, r_max)。

更進一步,對於位置 i,能夠裝的水為:

water[i] = min(                 # 左邊最高的柱子                 max(height[0..i]),                 # 右邊最高的柱子                 max(height[i..end])              ) - height[i]  

這就是本問題的核心思路,我們可以簡單寫一個暴力演算法:

有之前的思路,這個解法應該是很直接粗暴的,時間複雜度 O(N^2),空間複雜度 O(1)。但是很明顯這種計算r_max和l_max的方式非常笨拙,一般的優化方法就是備忘錄。

二、備忘錄優化

之前的暴力解法,不是在每個位置 i 都要計算r_max和l_max嗎?我們直接把結果都快取下來,別傻不拉幾的每次都遍歷,這時間複雜度不就降下來了嘛。

我們開兩個數組r_max和l_max充當備忘錄,l_max[i]表示位置 i 左邊最高的柱子高度,r_max[i]表示位置 i 右邊最高的柱子高度。預先把這兩個數組計算好,避免重複計算:

這個優化其實和暴力解法差不多,就是避免了重複計算,把時間複雜度降低為 O(N),已經是最優了,但是空間複雜度是 O(N)。下面來看一個精妙一些的解法,能夠把空間複雜度降低到 O(1)。

三、雙指針解法

這種解法的思路是完全相同的,但在實現手法上非常巧妙,我們這次也不要用備忘錄提前計算了,而是用雙指針邊走邊算,節省下空間複雜度。

首先,看一部分程式碼:

int trap(vector<int>& height) {      int n = height.size();      int left = 0, right = n - 1;        int l_max = height[0];      int r_max = height[n - 1];        while (left <= right) {          l_max = max(l_max, height[left]);          r_max = max(r_max, height[right]);          left++; right--;      }  }  

對於這部分程式碼,請問l_max和r_max分別表示什麼意義呢?

很容易理解,l_max是height[0..left]中最高柱子的高度,r_max是height[right..end]的最高柱子的高度。

明白了這一點,直接看解法:

你看,其中的核心思想和之前一模一樣,換湯不換藥。但是細心的讀者可能會發現次解法還是有點細節差異:

之前的備忘錄解法,l_max[i]和r_max[i]代表的是height[0..i]和height[i..end]的最高柱子高度。

ans += min(l_max[i], r_max[i]) - height[i];  

但是雙指針解法中,l_max和r_max代表的是height[0..left]和height[right..end]的最高柱子高度。比如這段程式碼:

if (l_max < r_max) {      ans += l_max - height[left];      left++;  } 

此時的l_max是left指針左邊的最高柱子,但是r_max並不一定是left指針右邊最高的柱子,這真的可以得到正確答案嗎?

其實這個問題要這麼思考,我們只在乎min(l_max, r_max)。對於上圖的情況,我們已經知道l_max < r_max了,至於這個r_max是不是右邊最大的,不重要,重要的是height[i]能夠裝的水只和l_max有關。

對於 l_max > r_max 的情況也是類似的。

這就是接雨水問題的全部內容,希望大家在演算法之路上繼續精進~

Tags: