背包DP

本文深入探讨了背包问题的三种类型:0-1背包、完全背包和多重背包,并通过实例解析了动态规划在解决这些问题中的应用。包括分割等和子集、目标和、一和零、单词拆分、完全平方数、零钱兑换等经典问题的解法。同时,展示了如何利用动态规划解决组合和排列问题。
摘要由CSDN通过智能技术生成

一、什么是背包问题

有n件物品,每件物品的体积为 w[i] ,价值为 v[i] ,求在有限的背包容量 W 下所能携带的最大价值。背包问题可分为 0 - 1 背包、完全背包与多重背包。

二、0 - 1 背包

每件物品最多选择一次。

dp[0] = 0;        //初始化

for (int i = 0; i < n; i++) {
    for (int j = W; j >= w[i]; j--) {    //逆序
        dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
    }
}

1、(416)分割等和子集

一个 只包含正整数 的 非空 数组。判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。


假设数组和为 t ,当 t 为奇数时,数组不可分割。

当 t 为偶数时,可将 t / 2视为W。

dp[0] = true;        //初始化

for (int i = 1; i < n; i++) {
    for (int j = t/2; j >= w[i]; j--) {        //倒序
        dp[j] |= dp[j - w[i]];
    }
}

2、(494)目标和

一个 非负整数数组 和一个目标数 S 。对于数组中的任意一个整数,可以从 + 或 - 中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

提示:

  • 数组非空,且长度不会超过 20 。
  • 初始的数组的和不会超过 1000 。
  • 保证返回的最终结果能被 32 位整数存下。

方法一:

当 S > 1000 || S < -1000 时,方法数为0。因此 -1000 ~ 1000 可以看作 W 的范围,开辟 dp[2001] 的数组,其中 0 ~ 1000 代表 -1000 ~ 0,1001 ~ 2000 代表 1 ~ 1000。

dp[w[0] + 1000] = 1;
dp[1000 - w[0]] += 1;    //1000 - w[0]与w[0] + 1000可能相同,故加1
for (int i = 1; i < n; i++) {
    int[] next=new int[2001];    //记录上一行的数据
    for (int j = 0; j <= 2000; j++) {
        if (j - nums[i] >= 0) next[j] += dp[j - nums[i]];       // +
        if (j + nums[i] <= 2000) next[j] += dp[j + nums[i]];    // -
    }
    dp = next;
}
return dp[S+1000];

方法二:

原命题等同于:设数组集合为 U,可将 U 分为两个子集 P 与 N,即:

\\sum(P)-sum(N)=S\\sum(P)-sum(N)+sum(P)+sum(N)=S+sum(P)+sum(N)\\2*sum(P)=S+sum(U)

命题转化为当 S+sum(U) 为偶数且 S\geqslant sum(U) 且 W=(S+sum(U))/2 时的背包问题。

dp[0] = 1;
for (int i = 0; i < n; i++) {
    for (int j = W; j >= nums[i]; j--) {
        dp[j] += dp[j - nums[i]];
    }
}

3、(474)一和零

一个二进制字符串数组 strs 和两个整数 m 和 n 。

求 strs 最大子集的大小,该子集中最多有 m 个 0 和 n 个 1 。


二维 0 - 1 背包,同样先遍历循环池,后遍历target。

int l = strs.length;
int[][] dp = new int[m + 1][n + 1];
int[][] cal = new int[l][2];
for (int i = 0; i < l; i++)
    for (int j = 0; j < strs[i].length(); j++) {
        if (strs[i].charAt(j) == '0') cal[i][0]++;
        else cal[i][1]++;
    }
for (int i = 0; i < l; i++)
    for (int j = m; j - cal[i][0] >= 0; j--)
        for (int k = n; k - cal[i][1] >= 0; k--)
            dp[j][k] = Math.max(dp[j][k], dp[j - cal[i][0]][k - cal[i][1]] + 1);
return dp[m][n];

三、完全背包

每件物品可选择多次。

求组合数外层遍历循环池,内层遍历target。

求排列数外层遍历target,内层遍历循环池。

dp[0] = 0;
for (int i = 1; i <= n; i++) {
    for (int j = w[i]; j <= W; j++) {    //顺序
        dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
    }
}

1、(139)单词拆分

给定一个非空字符串 s 和一个包含非空非重复单词的列表,判定 s 是否可以被拆分为一个或多个在字典中出现的单词。

拆分时可以重复使用字典中的单词。


考虑排列顺序的完全背包问题,外层循环为 target ,内层循环为选择池。

dp[0] = true;
int n = s.length();
for (int i = 1; i <= n; i++)
    for (String each : wordDict) {
        int l = each.length();
        if(i - l >= 0 && s.substring(i - l,i).equals(each))
            dp[i] |= dp[i-l];
    }
return dp[n];

2、(279)完全平方数

求和为 n 的完全平方数的最少数量。


外层循环为 target ,内层循环为选择池。

for (int i = 1; i <= n; i++) {
    dp[i] = i;    //最坏情况,都由1组成
    for (int j = 2; j * j <= i; j++)
        dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}

3、(322)零钱兑换

给定不同面额且数量不限的硬币 coins 和一个总金额 amount。计算可以凑成总金额所需最少的硬币个数,若无法凑成则返回 -1。


外层循环为 target ,内层循环为选择池。

Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++)
    for (int j : coins) {
        if(j <= i) dp[i] = Math.min(dp[i], dp[i - j] + 1);
    }
return dp[amount] > amount ? -1 : dp[amount];

4、(518)零钱兑换 II

给定不同面额且数量不限的硬币 coins 和一个总金额 amount。计算可以凑成总金额所需的硬币组合数。


外层循环为选择池 ,内层循环为 target。

dp[0] = 1;
for (int i = 0; i < coins.length; i++)
    for (int j = coins[i]; j <= amount; j++) {
        dp[j] += dp[j - coins[i]];
}
return dp[amount];

5、(377)组合总和

给定一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。计算 nums 中总和为 target 的元素组合的个数。


考虑排列顺序的完全背包问题,外层循环为 target ,内层循环为选择池。

dp[0] = 1;
for (int i = 1; i <= target; i++)
    for(int j : nums)
        if(j <= i) dp[i] += dp[i - j];
return dp[target];

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值