• 可能是01背包问题最全面的解析


    LeetCode链接

    01背包问题:在背包大小能装入的前提下,每个商品最多取一个。

    首先,我们有背包大小为4。

    • 商品1,商品2,商品3;
    • 重量依次为:1,3,4
    • 价格依次为:15,20,30;

    我们首先采用二维动态数组的方式来解决这个问题。

    那么我们首先要确定的是

    • 二维动态数组dp[i][j]表示的含义,i表示存入的当前商品i,j表示当前背包的重量。dp[i][j],在当前背包重量为j的前提下,装入商品i时,能产生的最大价值。
    • 我们做一个表格来表示

    第一行 0 表示,装入商品0,产生的价值,因为没有商品0,那么我们产生的价值肯定是0,因此第一行为0.

    第一列为0,表示如果我的背包大小为0时,所有商品都装不进去,因此产生的价值肯定是0.

    01234
    000000
    商品10
    商品20
    商品30

    那么,我在只能装入商品1时,能产生的价值是多少嗯?如下表所示,只要背包能装下商品1,其产生的最大价值都是15.(因为01背包问题,商品最多装入1次)

    01234
    000000
    商品1015151515
    商品20
    商品30

    那么,我在只能装入商品1和2时,能产生的价值是多少嗯?

    • 在背包大小为1或者2时,商品2都放不进去,因此dp[i][j] = dp[i-1][j]
    • 在背包大小>=商品2重量时,dp[i][j],如何更新呢?我们可以看出,
    • dp[i][j] = max(dp[i-1][j],当前商品价值+剩余空间还能装入的商品价值)。
    01234
    000000
    商品1015151515
    商品2015152035
    商品30

    那么,我在只能装入商品1,2,3时,能产生的价值是多少嗯?

    01234
    000000
    商品1015151515
    商品2015152035
    商品3015152035


    至此,我们更新dp的准则已经浮现出来了。

    • 当背包大小小于当前商品大小时,dp[i][j] = dp[i-1][j]
    • 在背包大小>=商品2重量时,dp[i][j] = max(dp[i-1][j],当前商品价值+剩余空间还能装入的商品价值)。

    【扩展】如何采用一维数组嗯?代码中包含的有,请自己分析一下

    1. package db;
    2. import java.util.Arrays;
    3. public class Package01 {
    4. public static void main(String[] args) {
    5. int[] weight = {1, 3, 4};
    6. int[] value = {15, 20, 30};
    7. int bagsize = 4;
    8. bag01Program2(weight, value, bagsize);
    9. }
    10. public static void bag01Program(int[] weight, int[] value, int size) {
    11. int lengthW = weight.length;
    12. int[][] dp = new int[lengthW + 1][size + 1];
    13. // 遍历顺序,先遍历物品,在遍历背包容量。
    14. for (int i = 1; i <= lengthW; i++) {
    15. for (int j = 1; j <= size; j++) {
    16. if (weight[i - 1] > j) {
    17. dp[i][j] = dp[i - 1][j];
    18. } else {
    19. // 上一次最大值,与本次要加入的值和剩余能装的最大值做对比。
    20. dp[i][j] = Math.max(dp[i - 1][j], value[i - 1] + dp[i - 1][j - weight[i - 1]]);
    21. }
    22. }
    23. }
    24. for (int[] ints : dp) {
    25. for (int j = 0; j < dp[0].length; j++) {
    26. System.out.print(ints[j] + "\t");
    27. }
    28. System.out.println();
    29. }
    30. }
    31. public static void bag01Program2(int[] weight, int[] value, int size) {
    32. int lengthW = weight.length;
    33. int[] dp = new int[size + 1];
    34. // 遍历顺序,先遍历物品,在遍历背包容量。
    35. for (int i = 0; i < lengthW; i++) {
    36. for (int j = size; j >=weight[i] ; j--) {
    37. dp[j] = Math.max(dp[j],value[i] + dp[j - weight[i]]);
    38. }
    39. System.out.print(Arrays.toString(dp) + "\t");
    40. System.out.println();
    41. }
    42. }
    43. }

    这里请思考一下?为什么dp采用一维后,内层循环要倒着写呢? 

    因为要满足:dp[j - weight[i]] 中j - weight[i] >= 0

    1.  for (int i = 0; i < lengthW; i++) {
    2.             for (int j = size; j >=weight[i] ; j--) {
    3.                 dp[j] = Math.max(dp[j],value[i] + dp[j - weight[i]]);
    4.             }

    【上面】LeetCode答案--采用一维数组。

    1. package db;
    2. import java.util.Arrays;
    3. public class CanPartition {
    4. public static void main(String[] args) {
    5. int[] nums = {1,2,5};
    6. boolean b = canPartition(nums);
    7. System.out.println("b = " + b);
    8. }
    9. public static boolean canPartition(int[] nums) {
    10. if(nums == null || nums.length == 0) return false;
    11. int n = nums.length;
    12. if (n == 2) {
    13. return nums[0] == nums[1];
    14. }
    15. int sum = 0;
    16. for(int num : nums){
    17. sum += num;
    18. }
    19. //总和为奇数,不能平分
    20. if(sum % 2 != 0) return false;
    21. // 相当于背包的总重量
    22. int target = sum / 2;
    23. // 1、创建动态数组。
    24. // 容量为j的背包,所背的物品价值可以最大为dp[j]。
    25. int[] dp = new int[target + 1];
    26. for (int i = 1; i < target; i++) {
    27. dp[i] = nums[0];
    28. }
    29. // 2、遍历方式
    30. for(int i = 1; i < n; i++){
    31. for(int j = target; j >= nums[i]; j--){
    32. dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
    33. }
    34. System.out.print(Arrays.toString(dp) + "\t");
    35. System.out.println();
    36. }
    37. return dp[target] == target;
    38. }
    39. }

    【总结】

    针对背包及其变形问题,

    • 第一步先确定背包的大小
    • 然后进行动态规划的三步走
      • 确定动态数组
      • 如何遍历
      • 动态数组的更新

  • 相关阅读:
    一起Talk Android吧(第三百九十九回:获取Bitmap的方法总结)
    算法通关18关 | 回溯模板如何解决复原IP问题
    多线程&并发篇---第十篇
    如何设置负载均衡EasySLB后端服务器网关
    摊销成本法(amortised cost method)
    x86体系结构(WinDbg学习笔记)
    OpenSSL CVE-2022-0778漏洞问题复现与非法证书构造
    如何使用 saplink 安装其他网站上提供的 ABAP 程序
    LeetCode 双周赛 104(2023/05/13)流水的动态规划,铁打的结构化思考
    顶刊示例-经济研究数据-全国、省、市-城市人均收入、农村人均收入面板数据
  • 原文地址:https://blog.csdn.net/abc123mma/article/details/127765271