雖然接觸動態規划算法已經有一段時間,給一個01背包問題,能夠做到一個表格簡單粗暴下去,然后求得結果,但心里總覺得對這個算法理解十分不到位,抱着對算法的熱愛,網上很多大牛的算法思維實在讓我佩服的五體投地。在此講一講動態規划中滾動數組的求解方法,算是對這個知識點做一個記錄,也希望有寫的不妥的地方,大家能不吝賜教。
首先,我們先看看“滾動數組”的例題,大家可以參考http://www.lintcode.com/en/problem/house-robber/
題意大概就是說:一個盜賊要去偷盜一系列房子的財物,房子之間有報警器,當且僅當兩個相鄰的房子同時被偷盜的時候會自動報警,每個房子有一定的價值量,請問盜賊怎么做才能偷到最大價值的量。
求解這類題目要注意以下四點要素(也可以說是動態規划題目的四點要素):
1、狀態(State)
用於存儲小規模問題的結果
2、方程(Function)
由狀態推出的一個方程,即怎么通過小的狀態來推出大的狀態
3、初始化(Initialization)
最初的狀態,作為方程的起點
4、結果(Result)
根據每步的狀態求出的結果
回到House-Robber這個題目,這個題目是個典型的滾動數組類型題。給定一個數組,我們不能夠取相鄰的兩個元素,怎么取法可以使取得的結果和最大。比如給定[4,9,1],我們可以取[4,1]或者取[9],很明顯一個結果是5,一個結果是9,所以我們這里的結果應該是后者的取法。在狀態的確立上,我們可以這么理解,用A[n]數組表示每個元素(下標從0開始),當我們遇到第 i 個元素的時候,我們是取不取,當我們取了第i-1個元素的時候,我們就不能取了,要不就去掉第i-1個元素,以便於取得第i個元素。如果我們用f[n]來代表當遇到第n個元素,我們做出行動(取還是不取)之后,能夠獲得的最大值,那當取到第i個元素的時候,我們就是判斷f[i-2] + A[i]和f[i-1]的大小,然后f[i] = max(f[i-2]+A[i],f[i-1]),你說是不是呢?我們無須去管i-3之前的元素是怎么取的,因為按照這個規律來,在f[i-2]已經包含了到i-2個元素位置能夠取得的最大量了。
至此,以上已經闡明了這個思路的【狀態】還有【方程】,剩下【初始化狀態】和【結果】,我們可以注意到確定第i個方程的結果,往前需要用到i-2的元素,所以第0個元素和第1一個元素需要作為初始化的內容,因為這兩個元素均不能用i-2的元素推倒出來,所以有f[0] = A[0],f[1] = max(A[1],A[0])。而結果就是我們對第n-1個元素做出行動之后的結果,該結果存儲在f[n-1]當中。
通過代碼實現:
long long houseRobber(vector<int> A){ if(0 == A.size()) return 0; int f[A.size()]; f[0] = A[0]; if(A.size()>1) f[1] = max(A[0],A[1]); for(int i =2;i<A.size();i++) f[i] = A[i] + f[i-2]> f[i-1]? A[i] +f[i-2]:f[i-1]; return f[A.size()-1]; }
這樣,在時間復雜度O(n)可以求得結果,空間復雜度為O(n)。
【在這里我稍微拓展一下,f[i]的結果是通過對比f[i-2]+A[i]和f[i-1]的結果得來的,那其實i-3及其以前的空間都不需要再用到,所以我們可以只用三個空間重復使用來表示每一個狀態,以達到空間復雜度為O(1)】
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上就是對一位數組的滾動數組算法求解過程,接下來探討一下二維數組的滾動數組求解過程,大家可以參考http://www.lintcode.com/en/problem/maximal-square/
題意:給定一個只包含01的二維數組,求出最大的正方形,使這個正方形的所有元素都為1
我們用Matrix[n][m]表示這個二維數組,dp[n][m]表示左上角最大符合條件的正方形邊長。首先,四個要素我們先來確定一下。【狀態】我們要確定第[i,j]的狀態要怎么確定呢,往前推一步有三個狀態,分別是[i-1,j],[i,j-1]和[i-1,j-1],這三個狀態同樣表示了左上角最大的正方形,我們通過圖來表示理解一下:
這里為了方便畫出了dp[i-1][j],dp[i][j-1],dp[i-1][j-1]相等的情況,可以很容易看出,當dp[i-1][j],dp[i][j-1],dp[i-1][j-1]相等的時候dp[i][j]等於dp[i-1][j],dp[i][j-1],dp[i-1][j-1]其中的一個加上1,如果dp[i-1][j],dp[i][j-1],dp[i-1][j-1]不相等,那dp[i][j]取決於dp[i-1][j],dp[i][j-1],dp[i-1][j-1]之中小的那一個,這里偷懶就不畫圖了,大家可以自己動手畫畫看,嘿嘿~。因此【狀態】確定了,【方程】也就容易得到dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1。【初始化狀態】可以想到從[1,1]開始才能應用這個方程(假定我們是從[0,0]向[n,m]逐漸確定狀態),所以i=0和j=0都是需要初始化的內容,這種情況下dp[i][j] = Matrix[i][j]。【結果】為dp矩陣中最大值的平方max{dp[n][m]}2。
代碼實現:
int maxSquare(vector<vector<int> > &matrix) { // write your code here int row = matrix.size(); int col = matrix[0].size(); int dp[row][col]; int _max = 0; for(int i =0;i<row;i++){ dp[i][0] = matrix[i][0]; dp[i][col-1] = matrix[i][col-1]; _max = dp[i][0]|dp[i][col-1]; } for(int i =0;i<col;i++){ dp[0][i] = matrix[0][i]; dp[row-1][i] = matrix[row-1][i]; _max = dp[0][i]|dp[row-1][i]; } for(int i =1;i<row;i++) for(int j = 1;j<col;j++){ if(1 == matrix[i][j]) dp[i][j] = min3(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) +1; else dp[i][j] = matrix[i][j]; _max = max(dp[i][j],_max); } return _max*_max; }
時間復雜度為O(n*m),空間復雜度為O(n*m)
【拓展:參考一位數組的拓展思路,我們確定[i,j]個狀態,分別用到[i-1,j],[i,j-1],[i-1,j-1]這三個狀態,所以我們可以想到可以用4個空間來重復表示各個狀態,但由於對於二維數組,當換行的時候到了行首會丟失這個狀態(因為4個空間在表示上一行末尾的四個狀態了),所以我們只能用兩行(2*n)個空間來維持這個狀態】下面給出拓展后的代碼,大家僅供參考,因為初始化狀態被寫在遍歷過程中,可能會造成混亂,請諒解:
int maxSquare(vector<vector<int> > &matrix) { int row = matrix.size(); int col = matrix[0].size(); int dp[2][col]; int _max = 0; for(int i =0;i<col;i++) dp[0][i] = matrix[0][i]; for(int i =1;i<row;i++) for(int j=0;j<col;j++){ if( 0 == j ) dp[1][j] = matrix[i][j]; else{ if(1 == matrix[i][j]) dp[1][j] = min3(dp[0][j],dp[1][j-1],dp[0][j-1])+1; else dp[1][j] = 0; } _max = max(_max,dp[1][j]); dp[0][j-1] = dp[1][j-1]; } dp[0][col-1] = dp[1][col-1]; return _max*_max; }
這樣空間復雜度可以降到O(m)。
以上為個人對滾動數組的求解過程的理解,每一句代碼均經過本人測試可用,如有問題,希望大家提出斧正。
尊重知識產權,轉載引用請通知作者並注明出處!