「程式碼隨想錄演算法訓練營」第三十二天 | 動態規劃 part5

云雀AC了一整天發表於2024-08-08

52. 攜帶研究材料

題目連結:https://kamacoder.com/problempage.php?pid=1052
文章講解:https://programmercarl.com/揹包問題理論基礎完全揹包.html
影片講解:https://www.bilibili.com/video/BV1uK411o7c9/
題目狀態:看題解過

思路:

0-1揹包問題中,每個物品只能選擇一次,即每個物品要麼被完全放入揹包,要麼完全不放入。內迴圈從大到小遍歷的原因主要是為了貪心選擇,即每次選擇當前價值最大的物品。這樣做的目的是為了在有限的揹包容量下儘可能多地裝入價值高的物品。如果揹包容量不足以放入當前價值最大的物品,就跳過這個物品,繼續考慮下一個價值稍低的物品。這種策略可以保證在給定的揹包容量下獲得最大的總價值。

for(int i = 0; i < weight.size(); i++) { // 遍歷物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍歷揹包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

而在完全揹包問題中,每個物品可以放入揹包任意多次。內迴圈從小到大遍歷的原因是,由於每個物品可以重複使用,我們希望首先考慮價值較低的物品,看是否能夠用較少的揹包容量達到接近目標價值的狀態。這樣,當我們考慮價值較高的物品時,可以更有效地利用揹包的剩餘容量,從而可能獲得更高的總價值。

// 先遍歷物品,再遍歷揹包
for(int i = 0; i < weight.size(); i++) { // 遍歷物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍歷揹包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

程式碼:

#include <iostream>
#include <vector>
using namespace std;

// 先遍歷揹包,再遍歷物品
void test_CompletePack(vector<int> weight, vector<int> value, int bagWeight) {

    vector<int> dp(bagWeight + 1, 0);

    for(int j = 0; j <= bagWeight; j++) { // 遍歷揹包容量
        for(int i = 0; i < weight.size(); i++) { // 遍歷物品
            if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    int N, V;
    cin >> N >> V;
    vector<int> weight;
    vector<int> value;
    for (int i = 0; i < N; i++) {
        int w;
        int v;
        cin >> w >> v;
        weight.push_back(w);
        value.push_back(v);
    }
    test_CompletePack(weight, value, V);
    return 0;
}

518. 零錢兌換 II

題目連結:https://leetcode.cn/problems/coin-change-ii/
文章講解:https://programmercarl.com/0518.零錢兌換II.html
題目難度:中等
影片講解:https://www.bilibili.com/video/BV1KM411k75j/
題目狀態:過

思路:

完全揹包問題,dp[j] 陣列表示總金額為 j 的時候有幾種不同的組合,因此 j+1 的組合情況就是在 j 的組合情況中加 1,因此 dp[j] 的構建方式就出來了:

dp[j] += dp[j - coins[i]];

程式碼:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;
        for(int i = 0; i < coins.size(); ++i) {
            for(int j = coins[i]; j <= amount; ++j) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

377. 組合總和 IV

題目連結:https://leetcode.cn/problems/combination-sum-iv/
文章講解:https://programmercarl.com/0377.組合總和Ⅳ.html
題目難度:中等
影片講解:https://www.bilibili.com/video/BV1V14y1n7B6/
題目狀態:看題解過

思路:

將“揹包”放在外圍迴圈,將“物品”放在內圍迴圈,這樣的話,每次迴圈都會重新遍歷一遍“物品”,可以將不同的排列順序加入組合中,如下示例:

假設 nums = [1, 2],target = 3。我們需要計算所有可能的組合。

  • 初始化:dp[0] = 1,因為只有一種方法可以得到和為 0,即不選任何數字。
  • 計算:
    • 目標和為 1:
      • 使用 1:dp[1] += dp[0],所以 dp[1] = 1。
    • 目標和為 2:
      • 使用 1:dp[2] += dp[1],所以 dp[2] = 1。
      • 使用 2:dp[2] += dp[0],所以 dp[2] = 2。
    • 目標和為 3:
      • 使用 1:dp[3] += dp[2],所以 dp[3] = 2。
      • 使用 2:dp[3] += dp[1],所以 dp[3] = 3。
  • 組合路徑:
    • 和為 1:
      [1]
    • 和為 2:
      [1, 1]
      [2]
    • 和為 3:
      [1, 1, 1]
      [1, 2]
      [2, 1]

透過上面的遍歷,即可得到所有排列。

程式碼:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for(int i = 0; i <= target; ++i) {
            for(int j = 0; j < nums.size(); ++j) {
                if(i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]])
                    dp[i] += dp[i - nums[j]];
            }
        }
        return dp[target];
    }
};

70. 爬樓梯

題目連結:https://kamacoder.com/problempage.php?pid=1067
文章講解:https://programmercarl.com/0070.爬樓梯完全揹包版本.html
題目狀態:過

思路:

這題的思路和上一題一樣:計算當前 dp 的時候要把比當前 dp 小的 dp 組合都加入裡面。

程式碼:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m;
    while (cin >> n >> m) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) { // 遍歷揹包
            for (int j = 1; j <= m; j++) { // 遍歷物品
                if (i - j >= 0) dp[i] += dp[i - j];
            }
        }
        cout << dp[n] << endl;
    }
}

相關文章