1. 程式人生 > >關於dfs和dp的思考

關於dfs和dp的思考

一、問題

問題1:給幾個數字,輸出排列的種樹;或者是八皇后問題

問題2:給定一字串,字串裡有*,*可以換成0或者1,輸出各種可能情況。(比如說,字串是01*78aljdou*a,可以變成01178aljdou1a、01078aljdou1a、01078aljdou0a、01078aljdou0a四種情況。

問題3:輸入為字串,是一串數字,輸出各種可能的ip地址(leetcode上Restore IP Addresses)

問題4:在leetcode上一道題,decode ways。  'A' -> 1    'B' -> 2    ...      'Z' -> 26

問題5:給一個數字,如果是奇數,可以加一和減一操作;如果是偶數,可以除以2。我們最終要讓這個數變為一,最少的步數是多少?

問題6:給定一串字串,再給一個字典,將字串分解為字典中存在的單詞,並將各種情況打印出來。

二、dfs

    面對上面這些題時,你仔細比較一下,都是輸出各種可能情況,可以看做一種排列問題。怎麼用dfs解決這種問題呢?    首先就用問題2舉個例子,當訪問到*的時候,我需要解決當前問題,可以把*換成0和1兩種情況,剩下的問題又成了獨立的問題,再遞迴呼叫處理剩下的問題。稍微用樸素的語言歸納一下,可以先列舉當前情況,再遞迴解決後面各種出現的情況.最後寫一個虛擬碼吧,來說明這種思路。

dfs(整個問題){
              取當前一部分,列舉當前情況,並解決
      dfs(剩下的問題)
}

三、dp

    在解決上面的第3、4個問題時,你會發現,有很多重複計算,所以說要保留重複計算的部分,我可以把它分解為一個個子問題,怎麼把一個子問題程式設計再大一點的問題呢?這就要尋找一種大問題與小問題之間的遞推關係。下面就用第四個問題來舉例,分析一下怎麼得到這個遞迴關係。
    以問題3為例,首先,從頭到尾掃這個String,從第一位到dp[i]這一位組成的String,有多少種解碼組合。(儲存重複運運算元問題
    可以有兩種情況:(分解子問題)1)只解析當前字元,有dp[i-1]種;2)解析i-1和i時,那麼dp[i-2]種

    那麼遞迴關係得到dp[i]=dp[i-1]+dp[i-2];(求解最優子結構

,這裡當然沒有通過比較最大最小得到最優,很自然的得到遞推)(形式有點類似斐波那契數列,http://blog.csdn.net/qomoman/article/details/38351041)

    同樣,在問題5中,同樣有類似的遞推關係,我們可以分析子問題。

3.1 dp總結

    我覺得可以寫成虛擬碼,如下
分解子問題,子問題求解,並且儲存子問題的解,防止重複運算
從眾多子問題中,並選取最優的子問題解,得到大問題與子問題的遞推關係
    從虛擬碼和上面分析問題的方法中,我們也可以有比較重要的三點:
1)分解子問題,怎麼劃分子問題?其實是一個分治的思想
2)儲存子問題的解,其實就是以空間換時間,防止重複求解
3)最優子結構,比較所有種可能性,得到全域性最優

四、dfs和dp的聯合使用

    當有些情況下,單用dfs很難求解,dfs容易造成很多重複的求解,而單獨用dp,則很難下手時,我們可以用dfs時,邊用dp(將重複求解的子問題儲存下來,以時間換空間),比如說問題6,這個題目很好的詮釋了dfs和dp的聯用。

五、感悟

    看了些動態規劃和dfs的題,有上面一點點兒感想,希望多拍磚,多提意見。