力扣刷题DAY8(动态规划)
一、01背包问题变式——子集和问题
目标和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = reduce(nums.begin(), nums.end());
int s = sum + target;
if (s % 2 != 0 || s < 0)
return 0;
else {
int t = s / 2;
int n = nums.size();
vector f(n + 1, vector<int>(t + 1));
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= t; j++) {
if (nums[i - 1] > j)
f[i][j] = f[i - 1][j];
else
f[i][j] = f[i - 1][j] + f[i-1][j - nums[i - 1]];
}
}
return f[n][t];
}
}
};
复杂度分析
- 时间复杂度:O(nt),其中 n 为 nums 的长度,t 为 nums 的元素和减去 target 的绝对值。
- 空间复杂度:O(nt)。保存多少状态,就需要多少空间。
思路
【视频】教你一步步思考动态规划!一个视频讲透!(Python/Java/C++/Go)https://leetcode.cn/problems/target-sum/solutions/2119041/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-s1cx
解释状态转移方程
01背包和子集和的关键区别
正是这个区别,导致了二者的状态转移方程不一样,初始化也不一样(01背包f[0][0]是0,而子集和是1)。
优化版(变成一维):
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = reduce(nums.begin(), nums.end()); // 计算 nums 的和
int s = sum + target;
// 边界条件检查
if (s < 0 || s % 2 != 0) {
return 0;
}
int t = s / 2; // 目标和
int n = nums.size();
// 初始化动态规划数组
vector<int> f(t + 1, 0);
f[0] = 1;
// 动态规划求解
for (int i = 1; i <= n; i++) {
for (int j = t; j >= 0; j--) { // j 从 0 开始
if (nums[i - 1] > j) {
f[j] = f[j]; // 不能选择当前元素
} else {
f[j] = f[j] + f[j - nums[i - 1]]; // 选择或不选择当前元素
}
}
}
return f[t]; // 返回结果
}
};
复杂度分析
- 时间复杂度:O(nt),其中 n 为 nums 的长度,t 为 nums 的元素和减去 target 的绝对值。
- 空间复杂度:O(t)。二维变一维。
二、完全背包问题
零钱兑换
这道题相当于让每个硬币的权重都为1,选取面值和为target的权重最小的完全背包问题。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
vector f(n + 1, vector<int>(amount + 1, INT_MAX / 2)); // 除 2 防止下面 + 1 溢出
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= amount; j++) {
f[i][j] = f[i - 1][j];
if (coins[i - 1] <= j)
f[i][j] = min(f[i][j], f[i][j - coins[i - 1]] + 1);
}
}
int ans = f[n][amount];
return ans < INT_MAX / 2 ? ans : -1;
}
};
复杂度分析
- 时间复杂度:O(n⋅amount),其中 n 为 coins 的长度。
- 空间复杂度:O(n⋅amount)。
初始化问题
分别考虑这三种特殊情况即可。
为什么是恰好等于amount,而不是小于等于?
优化版(变成一维)
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
vector<int> f(amount + 1, INT_MAX / 2); // 除 2 防止下面 + 1 溢出
f[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= amount; j++) {
f[j] = f[j];
if (coins[i - 1] <= j)
f[j] = min(f[j], f[j - coins[i - 1]] + 1);
}
}
int ans = f[amount];
return ans < INT_MAX / 2 ? ans : -1;
}
};
复杂度分析
- 时间复杂度:O(n⋅amount),其中 n 为 coins 的长度。
- 空间复杂度:O(amount)。
类似题目:
完全平方数
class Solution {
public:
int numSquares(int n) {
// 动态生成完全平方数
vector<int> wan;
for (int i = 1; i * i <= n; i++) {
wan.push_back(i * i);
}
int m = wan.size();
vector<vector<int>> f(m + 1, vector<int>(n + 1, INT_MAX / 2)); // 初始化 f 数组
f[0][0] = 0; // 凑成金额 0 需要 0 个完全平方数
// 动态规划求解
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= n; j++) { // j 从 0 开始
f[i][j] = f[i - 1][j]; // 不选择当前完全平方数
if (wan[i - 1] <= j) {
f[i][j] = min(f[i][j], f[i][j - wan[i - 1]] + 1); // 选择当前完全平方数
}
}
}
// 返回结果
int ans = f[m][n];
return ans < INT_MAX / 2 ? ans : -1;
}
};
复杂度分析
- 时间复杂度:O(N∗Sqrt(N))
- 空间复杂度:O(N∗Sqrt(N))
优化版:
class Solution {
public:
int numSquares(int n) {
// 动态生成完全平方数
vector<int> wan;
for (int i = 1; i * i <= n; i++) {
wan.push_back(i * i);
}
int m = wan.size();
vector<int> f(n + 1, INT_MAX / 2);
f[0] = 0; // 凑成金额 0 需要 0 个完全平方数
// 动态规划求解
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= n; j++) { // j 从 0 开始
f[j] = f[j]; // 不选择当前完全平方数
if (wan[i - 1] <= j) {
f[j] =
min(f[j], f[j - wan[i - 1]] + 1); // 选择当前完全平方数
}
}
}
// 返回结果
int ans = f[n];
return ans < INT_MAX / 2 ? ans : -1;
}
};