問題描述:在下圖裡我們有不同高度的擋板。這個圖片由一個整數陣列所代表,陣列中每個數是牆的高度。下圖可以表示為陣列(2、5、1、2、3、4、7、2)。假如開始下雨了,那麼擋板之間的水坑能夠裝多少水(水足夠多)呢?
下圖是裝滿水的情況,一個藍色格子代表一個單位的水。下圖中一共裝了10個單位的水。
問題分析:
先看看下圖,判斷哪個單元格的水能留下來。下圖中的兩個單元格,一個紅色的單元格和一個綠色的單元格,哪個單元格的水是溜走了,哪個單元格的水能留下來?
很明顯的,上圖中的紅色單元格的水會流走,綠色單元格的水會被留下來。
那麼,仔細看看這兩個單元格的區別在哪兒
區別就是,紅色單元格只有右邊的擋板比它高(不低於它),而綠色單元格左右兩邊都有擋板比它高(左邊最高是5,右邊最高是7)
這也就很好的理解了,如果水要能留下來,必須左右兩邊的擋板都比它高才行。(很明顯的,不管哪一側的擋板比水低,水就會朝哪個方向流出去)
於是,我給每個擋板定義了3個屬性
V屬性:本擋板的高度
L屬性:本擋板左側擋板的最高高度
R屬性:本擋板右側擋板的最高高度
那麼,該擋板上方能積水的充要條件就是:L>V並且R>V
如果該擋板能積水,則積水量為:Min(L-V,R-V)
那麼總的積水量就是所有擋板的積水量總和
問題就變成,如何求出每塊擋板的L屬性和R屬性
用V、L、R三個陣列標示擋板組的三個屬性。一共有N塊擋板,陣列的下標從0到N-1。
V(i)表示第i塊擋板的高度、L(i)表示第i塊擋板的L屬性、R(i)表示第i塊擋板的R屬性
可知的是L(0)=0,R(N-1)=0;
不失一般性,考慮第i塊擋板的L屬性(i>0)。L(i-1)表示第i-1塊擋板的L屬性。那麼,可知
如果L(i-1)>V(i-1),則L(i)=L(i-1)
如果L(i-1)≤V(i-1),則L(i)=V(i-1)
綜上所述:L(i)=Max(L(i-1),V(i-1))(i>0)
同理可述:R(i)=Max(R(i+1),V(i+1))(i<N-1)
而由於L(0)=0,R(N-1)=0。則說明第0、N塊擋板(最左和最右的擋板)是不會積水的
因此,計算L和R的屬性以及計算積水量的下標從1開始到N-2即可
程式碼如下:
Public Class clsFillWater
Public Shared Function FillWater2(ByVal ParamArray Nums() As Integer) As Integer
Dim L(Nums.Length - 1) As Integer
Dim R(Nums.Length - 1) As Integer
Dim I As Integer
Dim Result As Integer = 0
If Nums.Length < 3 Then Return 0
L(0) = 0
R(Nums.Length - 1) = 0
For I = 1 To Nums.Length - 2
L(I) = Math.Max(L(I - 1), Nums(I - 1))
R(Nums.Length - 1 - I) = Math.Max(R(Nums.Length - I), Nums(Nums.Length - I))
Next
For I = 1 To Nums.Length - 2
If L(I) > Nums(I) AndAlso R(I) > Nums(I) Then
Result += Math.Min(L(I), R(I)) - Nums(I)
End If
Next
Return Result
End Function
End Class
從程式碼看,該演算法的時間效率是O(N)的,是線性時間的。在文章 Twitter演算法面試題詳解(Java實現) 的評論中也有一個線性時間的演算法(效率相當,可能還優於本演算法),不過理解上不如這個簡單明瞭。