動態規划(下):如何求得狀態轉移方程並進行編程實現?
狀態轉移方程和編程實現
這里面求最小值的 min 函數里有三個參數,分別對應我們上節講的三種情況的編輯距離,分別是:替換、插入和刪除字符。在表格的右下角標出了兩個字符串的編輯距離 1。
我們假設字符數組 A[]和 B[]分別表示字符串 A 和 B,A[i]表示字符串 A 中第 i 個位置的字符,B[i]表示字符串 B 中第 i 個位置的字符。二維數組 d[,]表示剛剛用於推導的二維表格,而 d[i,j]表示這張表格中第 i 行、第 j 列求得的最終編輯距離。函數 r(i, j) 表示替換時產生的編輯距離。如果 A[i]和 B[j]相同,函數的返回值為 0,否則返回值為 1。
- 如果 i 為 0,且 j 也為 0,那么 d[i, j]為 0。
- 如果 i 為 0,且 j 大於 0,那么 d[i, j]為 j。
- 如果 i 大於 0,且 j 為 0,那么 d[i, j]為 i。
- 如果 i 大於 0,且 j 大於 0,那么 d[i, j]=min(d[i-1, j] + 1, d[i, j-1] + 1, d[i-1, j-1] + r(i, j))。
這里面最關鍵的一步是 d[i, j]=min(d[i-1, j] + 1, d[i, j-1] + 1, d[i-1, j-1] + r(i, j))。這個表達式表示的是動態規划中從上一個狀態到下一個狀態之間可能存在的一些變化,以及基於這些變化的最終決策結果。我們把這樣的表達式稱為狀態轉移方程。
有了狀態轉移方程,我們就可以很清晰地用數學的方式,來描述狀態轉移及其對應的決策過程,而且,有了狀態轉移方程,具體的編碼其實就很容易了。
如果我們使用動態規划法來實現編輯距離的測算,那就能確保查詢推薦的效率和效果。不過,基於編輯距離的算法也有局限性,它只適用於拉丁語系的相似度衡量,所以通常只用於英文或者拼音相關的查詢。如果是在中文這種亞洲語系中,差一個漢字(或字符)語義就會差很遠,所以並不適合使用基於編輯距離的算法。
實戰演練:錢幣組合的新問題
給定總金額和可能的錢幣面額,能否找出錢幣數量最少的獎賞方式?
假設這里我們有三種面額的錢幣,2 元、3 元和 7 元。為了湊滿 100 元的總金額,我們有三種選擇。
第一種,總和 98 元的錢幣,加上 1 枚 2 元的錢幣。如果湊到 98 元的最少幣數是 x_{1},那么增加一枚 2 元后就是 (x_{1} + 1) 枚。
第二種,總和 97 元的錢幣,加上 1 枚 3 元的錢幣。如果湊到 97 元的最少幣數是 x_{2},那么增加一枚 3 元后就是 (x_{2} + 1) 枚。
第三種,總和 93 元的錢幣,加上 1 枚 7 元的錢幣。如果湊到 93 元的最少幣數是 x_{3},那么增加一枚 7 元后就是 (x_{3} + 1) 枚。
比較一下以上三種情況的錢幣總數,取最小的那個就是總額為 100 元時,最小的錢幣數。換句話說,由於獎賞的總金額是固定的,所以最后選擇的那枚錢幣的面額,將決定到上一步為止的金額,同時也決定了上一步為止錢幣的最少數量。根據這個,我們可以得出如下狀態轉移方程:
其中,c[i]表示總額為 i 的時候,所需要的最少錢幣數,其中 j=1,2,3,…,n,表示 n 種面額的錢幣,value[j]表示第 j 種錢幣的面額。c[i - values(j)]表示選擇第 j 種錢幣的時候,上一步為止最少的錢幣數。需要注意的是,i - value(j) 需要大於等於 0,而且 c[0] = 0。
表格每一行表示獎賞的總額,前 3 列表示 3 種錢幣的面額,最后一列記錄最少的錢幣數量。表中的“/”表示不可能,或者說無解。
這張狀態轉移表同樣可以幫助你來理解狀態轉移方程的正確性。一旦狀態轉移方程確定了,要編寫代碼來實現就不難了。
總結
首先,如果一個問題有很多種可能,看上去需要使用排列或組合的思想,但是最終求的只是某種最優解(例如最小值、最大值、最短子串、最長子串等等),那么你不妨試試是否可以使用動態規划。
其次,狀態轉移方程是個關鍵。你可以用狀態轉移表來幫助自己理解整個過程。如果能找到准確的轉移方程,那么離最終的代碼實現就不遠了。當然,最好的方式,還是結合工作中的項目,不斷地實踐,嘗試,然后總結。