01背包
如:物品数量有限,重w[i],价值v[i],背包上限W的最大价值?目标价值的最小代价?
外层价值,内层物品,必须逆向枚举
例题:LC416分割等和子集
题目
将数组分为两个相等的子集,问是否有这种分法
思路
目标值为总和的一半,找到这样的子集。
等价于:数组每个元素看作一个物品的价值,看能不能凑到目标值。元素只有1个,01问题。
dp[i][j]表示[0:i]物品能否凑出目标值j,所求的是dp[n][target]
dp[i][j] = dp[i-1][j-num[i]]
遍历顺序:物品正序,值逆序。
值为什么逆序?因为物品不可以重复使用,前面的dp值如果先改变了就会影响到后面的。(优化成一维dp才需要逆序,否则不会影响正序也可以)
bool canPartition(vector<int>& nums) {
int sum = 0;
for(auto n:nums) sum += n;
if(sum % 2) return false;
sum /= 2;
vector<bool> dp(sum+1, false);
dp[0] = true;
for(int i = 0; i < nums.size(); ++i){
for(int j = sum; j >= nums[i]; --j){
if(dp[j-nums[i]])
dp[j] = true;
}
}
return dp[sum];
}
复杂度
- 时间O(n*target)
- 空间O(target)
例题:LC474一和零
思路
等价于给物品和物品价值, 你有约束条件的钱,问你最多买几个物品?只不过约束条件有两个
dp[i][j][k]表示解锁到第i个物品,有j个1和k个0 的最大数量
j>=n1&&k>=n0 dp[i][j][k] = max (dp[i-1][j-n1][k-n0]+1, dp[i-1][j][k])
else dp[i][j][k] = dp[i-1][j][k]
可以减小为2维,去掉物品i这一维度,但j和k的遍历要是逆序,因为不能取多次 。
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < strs.size(); ++ i){
int zero = 0;
int one = 0;
count(strs[i], zero, one);
for(int j = n; j >= one; -- j){
for(int k = m; k >= zero; -- k){
dp[k][j] = max(dp[k][j], dp[k - zero][j - one] + 1);
}
}
}
return dp[m][n];
}
void count(string n, int& zero, int& one){
for(auto c:n){
if(c == '1') ++ one;
else ++ zero;
}
}
复杂度
- 时间O(n*m*len+?)
- 空间O(n*m)
例题:LCR102目标和
题目
将正整数数组的数选择+或-,和为target,问有几种方法?
思路1:动态规划
正的和 - (负的和)= 总和 target = 正的和 + 负的和
正的和= (总和 + target)/2
回到了第一题,选数和为target,不过求的是方法数而不是可不可以。
dp[i][j]表示解锁前i个数,可以组成j的方法数
dp[i][j] += dp[i-1][j-n[i]]
int findTargetSumWays(vector<int>& nums, int target) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if((sum + target) % 2) return 0;//如果是奇数,说明target得不到
target = (sum + target) / 2;
vector<int> dp(target + 1, 0);
dp[0] = 1;
for(auto n:nums){
for(int i = target; i >= n; -- i){
dp[i] += dp[i - n];
}
}
return dp[target];
}
复杂度
- 时间O(n*target)
- 空间O(target)
如果不是正整数就不能用这个方法
完全背包
内循环正向枚举,可以重复无数次选。
例题:LC322零钱兑换
题目
给正整数数组,求组成等于amount的最小数量,可取无数次。
思路
dp[i][j] 表示 解锁到第i个数,总和为j的最小元素个数
dp[i][j] = min(dp[i-1][j - n] + 1, dp[i-1][j]);
int coinChange(vector<int>& coins, int amount) {
if(amount == 0) return 0;
vector<int> dp(amount + 1, 100000);
dp[0] = 0;
for(auto n : coins){
for(int i = n; i <= amount; ++ i){
dp[i] = min(dp[i - n] + 1, dp[i]);
}
}
return dp[amount] == 100000? -1 : dp[amount];
}
复杂度
- 时间O(n*amount)
- 空间O(amount)
例题:1449. 数位成本和为目标值的最大数字
题目
给数组cost[9],数字1-9的对应价值是cost[i-1],问你凑够价值target能够组成的最大数字。
思路
数字越多位数,值越大。所以先求组成target最多能用几个数字,完全背包问题。
然后
相同位数下,把大的数字放在高位,贪心求解即可。
string largestNumber(vector<int>& cost, int target) {
//物品只有10个,
vector<int> dp(target + 1, INT_MIN);//表示个数,只有可以凑成的数才会是正数
dp[0] = 0;
for (int i = 1; i <= 9; ++ i){
int u = cost[i - 1];//当前物品的价值
for (int j = u; j <= target; ++ j){//完全背包
dp[j] = max(dp[j], dp[j - u] + 1);
}
}
if (dp[target] < 0) return "0";//无法凑出
string res = "";
//排序,从9开始看是不是凑出的组合里的
for(int i = 9, j = target; i >= 1; -- i){
int u = cost[i - 1];//价值
while(j >= u && dp[j] == dp[j - u] + 1){//价值还有剩,且有这个数,也就是数字i的个数
res += to_string(i);
j -= u;
}
}
return res;
}
复杂度
- 时间O(n* target)
- 空间O(target)
多重背包
有限次选,转成01背包