算法训练1-day31-动态规划

35 阅读3分钟

1. 52. 携带研究材料(第七期模拟笔试)

定义:dp[i][j]表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少

递推公式:

  • 不放物品i:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]
  • 放物品i:背包空出物品i的容量后,背包容量为j - weight[i]dp[i][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值 递推公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); (注意,完全背包二维dp数组 和 01背包二维dp数组 递推公式的区别,01背包中是 dp[i - 1][j - weight[i]] + value[i])

初始化: dp[0][j],即:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 那么很明显当 j < weight[0]的时候,dp[0][j]应该是 0,因为背包容量比编号0的物品重量还小。 当j >= weight[0]时,dp[0][j] 如果能放下weight[0]的话,就一直装,每一种物品有无限个

AC代码:

#include <iostream>
#include <math.h>
#include <numeric>
#include <string>
#include <vector>

using namespace std;

int main() {
  int n = 0;
  int v = 0;
  cin >> n >> v;
  // vector<vector<int>> dp(n, vector<int>(v + 1, 0));
  vector<int> dp(v + 1, 0);
  vector<int> value(n);
  vector<int> weight(n);
  for (int k = 0; k < n; k++) {
    cin >> weight[k] >> value[k];
  }

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

  cout << dp[v];
  return 0;
}

2. 518. 零钱兑换 II

注意初始化;注意使用二维dp时,当j小于coins[i]dp[i][j] 要赋值为dp[i - 1][j]

当使用一维dp时: 如果求组合数就是外层for循环遍历物品,内层for遍历背包如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n = coins.size();
        // 0-i的硬币有几种方法组成j大小金额
        vector<vector<uint64_t>> dp(n, vector<uint64_t>(amount + 1));

        for (int j = 0; j <= amount; j += coins[0]) {
            dp[0][j] = 1;
        }
        for (int i = 0; i < n; ++i) {
            dp[i][0] = 1;
        }
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j <= amount; ++j) {
                if (coins[i] > j) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];
                }
            }
        }
        return dp[n - 1][amount];
    }
};

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n = coins.size();
        vector<uint64_t> dp(amount + 1);
        dp[0] = 1;
		
		// 先物品,后背包,就是求组合数
		// 假设:coins[0] = 1,coins[1] = 5。
		// 那么就是先把1加入计算,然后再把5加入计算,
		// 得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。
		// **所以这种遍历顺序中dp[j]里计算的是组合数!**
        for (int i = 0; i < n; ++i) {
            for (int j = coins[i]; j <= amount; ++j) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};
  1. 377. 组合总和 Ⅳ

dp[j]表示0-i的数字有几种构成j的组合

题目要求组合内数字顺序不同也算不同的组合,如:(1, 1, 2)(1, 2, 1),因此实际按排列来做

代码如下:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int n = nums.size();
        vector<uint64_t> dp(target + 1);
        dp[0] = 1;

        for (int j = 0; j <= target; ++j) {
            for (int i = 0; i < n; ++i) {
                if (j < nums[i])
                    continue;
                dp[j] += dp[j - nums[i]];
            }
        }

        return dp[target];
    }
};
  1. 57. 爬楼梯(第八期模拟笔试)
#include <iostream>
#include <math.h>
#include <numeric>
#include <string>
#include <vector>

using namespace std;

int main() {
  int n = 0;
  int m = 0;
  cin >> n >> m;
  // 每次至多爬j阶楼梯的情况下,爬到i阶有多少种方法
  vector<int> dp(n + 1, 0);
  dp[0] = 1;
  // 求排列,先遍历背包,再遍历物品
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      if (j > i)
        continue;
      dp[i] += dp[i - j];
    }
  }

  cout << dp[n];
  return 0;
}
```# 算法训练1-day30-动态规划