dp背包问题

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背包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值