草庐IT

动态规划--01背包问题详解

self-disciplin 2023-04-04 原文

代码随想录day42和day43 动态规划 模块01背包问题
“即使到不了远方,心中也要有远方的模样。”

文章目录

1. 01背包理论基础

1.1什么是背包问题


  背包问题大致可以分为以上几类,背包问题也就是选取物品放入重量为n的背包中,然后求背包的最大价值。

1.2二维dp数组01背包

  01背包问题—有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

例子:假设背包最大重量为4,然后有三个物品,看如何将物品放入背包中能获取最大价值。

还是可以用动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[i][j]对应的就是取索引[0,i]的物品,放入重量为j的背包中,得到价值dp[i][j]

   2. 确定递推公式

可以先将每种情况列举出来,然后再推导公式

由此可以推导出递推公式为dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
    每次遍历到dp[i][j]的时候都有两种取值情况dp[i-1][j]表示不放物品时的情况  
   dp[i-1][j-weight[i]]+value[i],weight[i]表示i的重量,value[i]表示i的价值。这就是表示添加新物品的情况
在上面两种情况中取最值

  3. dp数组的初始化问题

当背包重量为0时,可以初始化dp[i][0]=0;

  4.确定遍历顺序

这题的从前往后面遍历,可以先遍历背包再遍历物品也可以先便利物品再遍历背包,一般是先遍历物品

  5. 举例推导dp公式

代码示例

    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagsize = 4;
        testweightbagproblem(weight, value, bagsize);
    }

    public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
        int wlen = weight.length, value0 = 0;
        //定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
        int[][] dp = new int[wlen + 1][bagsize + 1];
        //初始化:背包容量为0时,能获得的价值都为0
        for (int i = 0; i <= wlen; i++){
            dp[i][0] = value0;
        }
        //遍历顺序:先遍历物品,再遍历背包容量
        for (int i = 1; i <= wlen; i++){
            for (int j = 1; j <= bagsize; j++){
                if (j < weight[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
                }
            }
        }
        //打印dp数组
        for (int i = 0; i <= wlen; i++){
            for (int j = 0; j <= bagsize; j++){
                System.out.print(dp[i][j] + " ");
            }
            System.out.print("\n");
        }
    }

1.3一维dp数组(滚动数组)01背包

  上面用二维数组来解决01背包问题,其实也可以用一维数组(也叫做滚动数组)来解决01背包问题。其核心也就是将二维数组压缩成一维数组。
假设背包最大重量为4,然后有三个物品,看如何将物品放入背包中能获取最大价值。

  那还是按照动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[j]对应的就是背包重量为j的时候对应的最大价值dp[j]

   2. 确定递推公式

可以先将每种情况列举出来,然后再推导公式

dp[j]是由dp[j-weight[i]]决定的,递推公式可以如下
dp[j]=Math.max(dp[j],dp[j-weight[i]]+value[i])

  3. dp数组的初始化问题

当背包重量为0时,可以初始化dp[0]=0;

  4.确定遍历顺序

这题先遍历物品的时候可以从前面往后面遍历,然后再背包的时候得从后面往前面进行倒序遍历。如果是顺序遍历的话物品就会重复添加,因为每个物品只能用一次,以免影响其他结果

  5. 举例推导dp公式

代码示例

    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWight = 4;
        testWeightBagProblem(weight, value, bagWight);
    }

    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
        int wLen = weight.length;
        //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
        int[] dp = new int[bagWeight + 1];
        //遍历顺序:先遍历物品,再遍历背包容量
        for (int i = 0; i < wLen; i++){
            for (int j = bagWeight; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //打印dp数组
        for (int j = 0; j <= bagWeight; j++){
            System.out.print(dp[j] + " ");
        }
    }

2.leetcode 416.分割等和子集

力扣题目链接

2.1 详细思路及思考难点

  这题可以将其划分为01背包问题,给定一个数组,判断是否能将数组分成两个子集和相同的两个数组。要将数组分成两个相等的子集的话,数组中的和sum也就是必须是偶数(这是第一个判断条件),然后令target=sum/2;这就是子集的和,把target想象成一个背包的最大重量,现在就是让背包里面的物品的重量刚好能达到背包的最大重量,如果能达到就返回true,反之,return false。

2.2具体步骤及代码实现

  按照动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[j]对应的就是背包重量为j的时候对应的最大价值dp[j]

   2. 确定递推公式

dp[j]是由dp[j-weight[i]]决定的,递推公式可以如下
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]),这里nums[i]既可以表示重量也可以表示价值

  3. dp数组的初始化问题

当背包重量为0时,可以初始化dp[0]=0;

  4.确定遍历顺序

这题先遍历物品的时候可以从前面往后面遍历,然后再背包的时候得从后面往前面进行倒序遍历。因为每个物品只能用一次,以免影响其他结果

  5. 举例推导dp公式

代码实现

class Solution {
    public boolean canPartition(int[] nums) {
     if(nums==null || nums.length==0)return false;
     int sum=0;
     for(int a:nums){
        sum+=a;
     }
     if(sum%2!=0)return false;
     int targrt=sum/2;
     int[] dp=new int[targrt+1];
     //dp[0]=0;
     for(int i=0;i<nums.length;i++){
         for(int j=targrt;j>=nums[i];j--){
            dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
         }
     }
     return dp[targrt]==targrt;
    }
}

3.leetcode 1049.最后一块石头的重量

力扣题目链接

3.1详细思路及思考难点

  这题思路跟416.分割子集问题基本一致,可以将所有石头分成两份和相同的子集和,target=sum/2,然后将石头存入dp[target]中所能满足的最大价值,最终返回的结果是(sum-dp[target])-dp[target],(sum-dp[target])表示没有装入背包的所有的集合,然后减去装入背包的,就是碰撞之后剩下的量。

3.2具体步骤及代码实现

  按照动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[j]对应的就是背包重量为j的时候对应的最大石头量dp[j]

   2. 确定递推公式

dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  3. dp数组的初始化问题

当背包重量为0时,可以初始化dp[0]=0;

  4.确定遍历顺序

这题先遍历物品的时候可以从前面往后面遍历,然后再背包的时候得从后面往前面进行倒序遍历。因为每个物品只能用一次,以免影响其他结果

  5. 举例推导dp公式

代码实现

class Solution {
    public int lastStoneWeightII(int[] stones) {
    int sum=0;
    for(int a:stones){
     sum+=a;
    }
    int target=sum>>1;
     int[] dp=new int[target+1];
     dp[0]=0;
     for(int i=0;i<stones.length;i++){
         for(int j=target;j>=stones[i];j--){
             dp[j]=Math.max(dp[j],dp[j-stones[i]]+stones[i]);
         }
     }
     return sum-dp[target]-dp[target];
    }
}

4.leetcode 494.目标和

力扣题目链接

4.1详细思路及思考难点

  将nums中的数值添加正负号之后,设正数和为x,那么负数的和就是sum-x(sum是nums数组中所有元素的和)。x-(sum-x)=target ==>x=(sum+target)/2。然后将x转换成填满大小为x的背包,需要dp[j]种方法。

4.2具体步骤及代码实现

  按照动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[j]对应的就是背包重量为j的时候对应的最大价值dp[j]

   2. 确定递推公式

根据下面得出递推公式为dp[j]+=dp[j-nums[i]]

  • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 dp[5]。
  • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 dp[5]。
  • 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 dp[5]
  • 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 dp[5]
  • 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 dp[5]

  3. dp数组的初始化问题

当背包重量为0时,可以初始化dp[0]=0;

  4.确定遍历顺序

这题先遍历物品的时候可以从前面往后面遍历,然后再背包的时候得从后面往前面进行倒序遍历。因为每个物品只能用一次,以免影响其他结果

代码实现

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
     int sum=0;
     for(int a:nums){
        sum+=a;
     }
     int zs=(sum+target)/2;
      if((target+sum)%2!=0) return 0;
     if(zs<0)zs=-zs;
     int[] dp=new int[zs+1];
     if(target>sum)return 0;
     dp[0]=1;
     for(int i=0;i<nums.length;i++){
         for(int j=zs;j>=nums[i];j--){
             dp[j]+=dp[j-nums[i]];
         }
     }
     return dp[zs];
    }
}

5.leetcode 474.一和零

力扣题目链接

5.1详细思路及思考难点

  这题用二维dp数组来做,把每个字符串当作单独的物品,总共的0和1的个数当作背包的容量。

5.2具体步骤及代码实现

  按照动态规划的做题步骤来分析

   1.确定dp数组以及下标的含义

dp[i][j]表示当有i个0和j个1时的最大子集的大小

   2. 确定递推公式

根据下面得出递推公式为dp[i][j]=Math.max(dp[i][j],dp[i-zero][j-one]+1)

  3. dp数组的初始化问题

初始为0就可以

  4.确定遍历顺序

遍历字符串的时候顺序遍历,遍历0和1的时候倒叙

  5.推导dp数组

代码实现

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
       int[][] dp=new int[m+1][n+1];
       int zero,one;
       for(String s : strs){
           zero=0;
           one=0;
           for(char c:s.toCharArray()){
              if(c=='0'){
                  zero++;
              }else if(c== '1'){
                  one++;
              }
           }
           for(int i=m;i>=zero;i--){
               for(int j=n;j>=one;j--){
                   dp[i][j]=Math.max(dp[i][j],dp[i-zero][j-one]+1);
               }
           }
       }
       return dp[m][n];
    }
}

有关动态规划--01背包问题详解的更多相关文章

  1. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

  2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  3. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  4. ruby - Fast-stemmer 安装问题 - 2

    由于fast-stemmer的问题,我很难安装我想要的任何ruby​​gem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=

  5. ruby - 安装 Ruby 时遇到问题(无法下载资源 "readline--patch") - 2

    当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  8. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  9. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  10. git使用常见问题(提交代码,合并冲突) - 2

    文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

随机推荐