# 动态规划的解题步骤

  • 设计状态
  • 写出状态转移方程
  • 设定初始状态
  • 执行状态转移
  • 返回最终的解

# 动态规划和贪心的区别

  • 贪心:每一步都做出当时看起来最佳的选择,也就是说,它总是做出局部最优的选择。
  • 动态规划:每一个状态一定是由上一个状态推导出来的。

# 01背包问题详解

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

状态转移方程

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

# 二维数组方法

vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<vector<int>> dp(weight.size(), vector<int>(bagWeight + 1, 0));

for (int j = weight[0]; j <= bagWeight; j++) {
    dp[0][j] = value[0];
}

for (int i = 1; i < weight.size(); i++) {
    for (int j = 0; j <= bagWeight; j++) {
        if (j < weight[i]) dp[i][j] = dp[i - 1][j];
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 一维数组方法

vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<int> dp(bagWeight + 1, 0);
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]);
    }
}
1
2
3
4
5
6
7
8
9

# 为什么一维数组需要倒序,而二维数组不需要倒序?

  • 对于二维数组来说,上一次的状态是由dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!
  • 而对于一维数组来说,如果正序遍历,会导致复用上一状态的结果,也就是状态会被累加

# 一维dp可不可以调换遍历顺序?

答案必须是不可以!如果调换顺序,会导致dp[j]只会被更新一次,因为dp[j]依赖的上一级状态并没有更新,这就导致了该背包只会被放入一个或者0个物品。

# 完全背包问题详解

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。

状态转移方程

dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);

# 二维数组方法

vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<vector<int>> dp(weight.size(), vector<int>(5, 0));

for (int j = weight[0]; j <= bagWeight; j++) {
    dp[0][j] = max(value[0], value[0] + dp[0][j - weight[0]]);
}

for (int i = 1; i < weight.size(); i++) {
    for (int j = 0; j <= bagWeight; j++) {
        if (j < weight[i]) dp[i][j] = dp[i - 1][j];
        else dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 一维数组方法

  • 先物品,后容量
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<int> dp(bagWeight + 1, 0);
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]);
    }
}
1
2
3
4
5
6
7
8
9
  • 先容量,后物品
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
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]);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

二者区别

  • 如果是普通的纯完全背包,那么这两个for循环顺序没有影响
  • 如果需要计算组合或者排序数量那么就有影响
    • 如果求组合数就是外层for循环遍历物品,内层for遍历背包
    • 如果求排列数就是外层for遍历背包,内层for循环遍历物品
Last Updated: 4/9/2024