1. 程式人生 > >動態規劃之揹包問題

動態規劃之揹包問題

1. 一維揹包問題

0-1揹包問題

給定一個載重量為CC的揹包,有nn個物品,每個物品的重量為wiw_{i},每個物品的價值為viv_{i},如何往揹包中裝物品使得揹包中的總價值最大化。

maxV=Σvixis.t.ΣwixiCxi=0or1\begin{matrix} & max ~~V = \Sigma{ v_{i}x_{i}} \\ & s.t.~~\Sigma{ w_{i}x_{i}} \le C\\ & ~~~~~~~~~~x_{i}={0~or ~1} \\ \end{matrix}

ixiCxi=0or1

可以用動態規劃的方法對該問題進行求解。

1.1. 遞推表示式

f(i,j)~f(i,j)~表示前i~i~物品中選擇物品放入揹包使得總重量不超過j~j~,揹包的最大總價值。因此有如下所示的遞推公式:

f(i,j)={max[f(i1,jwi)+vi,f(i1,j)]jwkf(i1,j)j<wk f(i, j)= \begin{cases} max~[f(i-1, ~j-w_{i})+v_{i}, f(i-1,~j)] \quad & j \ge w_{k}\\ f(i-1,~j)\quad & j < w_{k} \end{cases}

(1) 解釋

揹包的最大總價值f(i,j)~f(i,j)~有兩種情況可以達到,一是,不放第i~i~件物品,前i1~i-1~件物品已經把揹包填滿了,放不下第ii件物品了,那該值就是f(i1,j)~f(i-1,~j)~;二是,放第i~i~件物品,那麼前i1~i-1~件物品所佔的空間只能是jwi~j-w_{i}~,那該值就是f(i1,jwi)+vi~f(i-1, ~j-w_{i})+v_{i}~

wi)+vi。因此,f(i,j)~f(i,j)~就是這兩個值中的較大值。

1.2. 動態規劃實現

int maxValue(const std::vector<int>& weights, const std::vector<int>& values, const int& C) {
    std::vector<std::vector<int> > dp(weights.size() + 1, std::vector<int>(C + 1, 0));
    for (int i = 1; i < dp.size(); ++i) {
        for (int j = 1; j < dp[i].size(); ++j) {
            if (j >= weights[i-1])
                dp[i][j] = max(dp[i-1][j], dp[i-1][j - weights[i-1]] + values[i-1]);
        }
    }
    return dp[weights.size()][C];
}

時間複雜度為O(nC)~O(nC)~,空間複雜度也為O(nC)~O(nC)~

1.3. 降低空間複雜度

能不能降低空間複雜度呢?答案是肯定的。再觀察式(1),可以發現f(i,j)f(i,j)的值僅和f(i1,)f(i-1,*)有關。因此可以壓縮這個維度,僅用一個一維陣列表示。

int maxValue(const std::vector<int>& weights, const std::vector<int>& values, const int& C) {
    std::vector<int> dp(C + 1, 0);
    for (int i = 1; i < weights.size(); ++i) {
        // reverse order
        for (int j = C; j >= 1; --j) {
            if (j >= weights[i-1])
                dp[j] = max(dp[j], dp[j - weights[i-1]] + values[i]);
        }
    }
    return dp[C];
}

注意在內層迴圈中使用倒序,因為在dp[j]的取值會影響其後面的取值,如果從前向後遍歷,則前面的值已經重新整理了,會影響後面的值,因此在遍歷時要採用倒序。

2. 二維揹包問題

For now, suppose you are a dominator of m 0s and n 1s respectively. On the other hand, there is an array with strings consisting of only 0s and 1s.

Now your task is to find the maximum number of strings that you can form with given m 0s and n 1s. Each 0 and 1 can be used at most once.

2.1 遞迴公式

f(i,j,k)~f(i, j, k)~表示在前i~i~字串中,0不超過j~j~個,1不超過k~k~個條件下,最多的字串數量。

f(i,j,k)={max[f(i1,jzerosi,konesi)+1,f(i1,j,k)]jzerosi,konesif(i1,j,k)others f(i, j, k)= \begin{cases} max~[f(i-1, ~j-zeros_{i},~k-ones_{i})+1, f(i-1,~j,~k)] \quad &amp; j\ge zeros_{i},~k \ge ones_{i}\\ f(i-1,~j,~k)\quad &amp; others \end{cases} (2)

同樣地,可以利用二維陣列實現動態規劃的演算法以降低空間複雜度。

int findMaxForm(vector<string>& strs, int m, int n) {
    if (strs.empty()|| m < 0 || n < 0) return 0; 
    vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
    // dynamic programming
    for (int i = 0; i < strs.size(); i++) {
        int zeros = 0, ones = 0;
        for (char c : strs[i]) {
            if (c == '0') zeros++;
            if (c == '1') ones++;
        }
        for (int j = m; j >= zeros; j--) {
            for (int k = n; k >= ones; k--) {
                    dp[j][k] = max(dp[j][k], dp[j-zeros][k-ones] + 1);
            }
        }
    }
    return dp[m][n];
}