一些可以用動態規劃(DP)算法解決的問題(C++)

一、動態規劃問題
來源:暴力搜索->記憶式搜索->經典的動態規劃->改進的動態規劃。這也是動態規劃問題的求解步驟。
本質:利用空間來換取時間。把一個問題分解爲相同的子問題,這些子問題的求解是有順序的,按順序一步一步求解,前面的步驟和決策使得問題的狀態轉移到當前狀態,當前狀態再做出最優的決策,使問題轉移到下一個狀態,當問題進入最後一個狀態時的解就是原問題的解。

二、練習題
1、有數組penny,penny中所有的值都爲正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim(小於等於1000)代表要找的錢數,求換錢有多少種方法。

解法(1):按照經典的動態規劃步驟進行,空間複雜度爲O(n*aim)
class Exchange {
public:
    int countWays(vector<int> penny, int n, int aim) {
        if (penny.empty()||n == 0)
            return 0;
        vector<vector<int> > dp(n,vector<int>(aim+1));
        for (int i = 0;i < n;i++) {
            dp[i][0] = 1;
        }
        for (int j = 1;j < aim+1;j++) {
            dp[0][j] = j%penny[0] == 0?1:0;
        }   
         
        for (int i = 1;i < n;i++) {
            for (int j = 1;j < aim+1;j++) {
                dp[i][j] = (j-penny[i]) >= 0?(dp[i-1][j] + dp[i][j-penny[i]]):dp[i-1][j];                   
            }
        }
        return dp[n-1][aim];
    }
};

解法(2):步驟與經典的動態規劃問題一樣,但是空間複雜度僅爲O(aim)。其實在求dp矩陣時,都是根據上一行的值迭代出當前行的值,所以完全可以只用一維矩陣來存儲,不斷地更新一維矩陣即可。
class Exchange {
public:
    int countWays(vector<int> penny, int n, int aim) {
        vector<int> dp(aim + 1);
        for (int i = 0; i <= aim; i++)
            if (i % penny[0] == 0)
                dp[i] = 1;
 
        for (int i = 1; i < n; i++)
            for (int j = 1; j <= aim; j++)
                if ( j >= penny[i])
                    dp[j] += dp[j - penny[i]];
        return dp[aim];
    }
};

2、有n級臺階,一個人每次上一級或者兩級,問有多少種走完n級臺階的方法。
解法:f(n)=f(n-1)+f(n-2)。如果直接用遞歸式求解,中間有很多重複的計算,f(n-1)分支計算過的還得在f(n-2)分支計算一次。然後狀態之間的依賴關係是很容易找出了,用動態規劃法,一步一步記錄相鄰兩個狀態即可,下一個狀態等於這兩個狀態之和。
class GoUpstairs {
public:
    int countWays(int n) {
        vector<int> dp(2,0);
        dp[0] = 1;
        dp[1] = 2;
        int temp;
        for (int i = 3;i <= n;i++) {
            temp = dp[0];
            dp[0] = dp[1];
            dp[1] = (dp[1]+temp)%1000000007;
        }
        return dp[1]%1000000007;
    }
};


3、有一個矩陣map,它每個格子有一個權值。從左上角的格子開始每次只能向右或者向下走,最後到達右下角的位置,路徑上所有的數字累加起來就是路徑和,返回所有的路徑中最小的路徑和。
解法:f(n,m)=min(f(n-1,m),f(n,m-1))+map[n][m]。遞歸式同樣包含很多重複計算,可以根據狀態之間的依賴關係一步一步計算出來。走到第一行每個格子的最小路徑和很容易求出。根據第一行可以依次求出第二行,依次進行直到計算到最後一行。

class MinimumPath {
public:
    int getMin(vector<vector<int> > map, int n, int m) {
        vector<int> dp(m,0);
        dp[0] = map[0][0];
        for (int i = 1,j = 0;i < m;i++,j++) {
            dp[i] = map[0][i]+dp[j];
        }
         
        for (int i = 1;i < n;i++) {
            dp[0] += map[i][0];
            for (int j = 1;j < m;j++) {
                dp[j] = min(dp[j],dp[j-1])+map[i][j];
            }
        }
        return dp[m-1];
    }
};


4、(經典的LIS問題)請設計一個儘量優的解法求出序列的最長上升子序列的長度
解法:用dp數組的dp[i]記錄下以A[i]結尾的遞增子序列中最長的長度,計算dp[i+1]時,遍歷A[0~i]找到比A[i+1]小的元素,再比較與這些元素對應的dp數組中的值,找到最大的一個再加1,賦值給dp[i+1]。
class LongestIncreasingSubsequence {
public
:
    int getLIS(vector<int> A, int n) {
        if (A.empty()||n == 0)
            return 0;  
        vector<int> dp(n,0);
        dp[0] = 1;
        int resMax = 0;
        for (int i = 1;i < n;i++) {
            int tempMax = 0;
            for (int j = 0;j < i;j++) {
                if (A[i] > A[j])
                    tempMax = max(tempMax,dp[j]);
            }
            dp[i] = ++tempMax;
            resMax = max(resMax,dp[i]);
        }
        return resMax;
    }
};

5、給定兩個字符串A和B,返回兩個字符串的最長公共子序列的長度。例如,A="1A2C3D4B56",B="B1D23CA45B6A",123456或者12C4B6都是最長公共子序列。
解法經典的動態規劃題目(LCS。利用動態規劃表求解。dp[i][j]表示A[0~i]和B[0~j]的最長公共子序列長度。如果A[i]=B[j], dp[i][j]一定是dp[i-1][j-1]+1,若A[i]!=B[j],則dp[i][j]要麼是dp[i-1][j],要麼是dp[i][j-1]。

(1)常規解法:對第一行和第一列的處理不夠巧妙。
class LCS {
public:
    int findLCS(string A, int n, string B, int m) {
         if (A.empty()||n==0||B.empty()||m==0)
            return 0;
        vector<vector<int> > dp(n,vector<int>(m));
         
        for (int i = 0;i < m;i++) {
            if (A[0] == B[i]) {
                for (int j = i;j < m;j++)
                    dp[0][j] = 1;
                break ;
            }               
        }
         
        for (int i = 0;i < n;i++) {
            if (B[0] == A[i]) {
                for (int j = i;j < n;j++)
                    dp[j][0] = 1;
                break ;
            }               
        }
         
        for (int i = 1;i < n;i++) {
            for (int j = 1;j < m;j++) {
                if (A[i] == B[j])
                    dp[i][j] = dp[i-1][j-1]+1;
                else
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return dp[n-1][m-1];
    }
};

(2)最優解:dp矩陣多申請了一行和一列,從而將第一行和第一列的處理融合到後序的處理中。
class LCS {
public:
    int findLCS(string A, int n, string B, int m) {         
        vector<vector<int> > dp(n+1,vector<int>(m+1,0)); 
        for (int i =1;i<=n ;++i){             
            for (int j=1; j<=m; ++j){
                if (A[i-1] == B[j-1]){
                    dp[i][j]  = dp[i-1][j-1]+1;
                }
                else {
                    dp[i][j] = max( dp[i-1][j] ,dp[i][j-1]);
                }                 
            }
        }
        return dp[n][m];
    }
};

6、一個揹包有一定的承重cap,有N件物品,每件都有自己的價值,記錄在數組v中,也都有自己的重量,記錄在數組w中,每件物品只能選擇要裝入揹包還是不裝入揹包,要求在不超過揹包承重的前提下,選出物品的總價值最大。
解法:經典的01揹包問題。同樣利用動態規劃表來求解。
(1)常規解法:用常規的二維矩陣dp作爲動態規劃表,第一行第一列單獨提前處理。空間複雜度略高。
class Backpack {
public:
    int maxValue(vector<int> w, vector<int> v, int n, int cap) {
        if (w.empty()||v.empty()||n==0||cap==0)
            return 0;
        vector<vector<int> > dp(n,vector<int>(cap+1));
         
        for (int j = 1;j < cap+1;j++) {
            dp[0][j] = w[0] <= j?v[0]:0;
        }
        for (int i = 0;i < n;i++) {
            dp[i][0] = 0;
        }
         
        for (int i = 1;i < n;i++) {
            for (int j = 1;j < cap+1;j++) {
                if (w[i] > j)
                    dp[i][j] = dp[i-1][j];
                else
                    dp[i][j] = max(dp[i-1][j],v[i]+dp[i-1][j-w[i]]);
            }
        }
        return dp[n-1][cap];
    }
};

(2)更優的解法:用一維矩陣dp作爲動態規劃表。每次用複製構造函數記錄上一行的求解結果,根據上一行的求解結果求出當前行的結果後再記錄到dp矩陣中。空間複雜度略好。
class Backpack {
public
:
    int maxValue(vector<int> w, vector<int> v, int n, int cap) {
        if (w.empty()||v.empty()||n==0||cap==0)
            return 0;
        vector<int> dp(cap+1,0);        
        for (int i = 0;i < n;i++) {
             vector<int> last(dp);
            for (int j = 1;j < cap+1;j++) {
                dp[j] = j < w[i]?last[j]:max(last[j],v[i]+last[j-w[i]]);                
            }
        }
        return dp[cap];
    }
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章