代码随想录刷题| 01背包理论基础 LeetCode 416. 分割等和子集

news2024/11/29 6:38:12

目录

01背包理论基础

二维dp数组

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

2、确定递推公式

3、dp数组如何初始化

4、确定遍历顺序

5、打印dp数组

最终代码

一维dp数组

1、确定dp数组的定义

2、确定递推公式

3、初始化dp数组

4、遍历顺序

5、打印dp数组

最终代码

416. 分割等和子集 

思路

分割等和子集


01背包理论基础

  • 01背包问题
    • 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大
  • 01背包问题的解法:
    • 暴力解法:回溯算法
      • 有n个物品,其中每件物品的状态只有取和不取,使用回溯法搜索出所有的情况,所以时间复杂度为O(2^n)
      • 使用回溯算法的时间复杂度是指数级别,所以需要使用动态规划的解法来进行优化
    • 动态规划
      • 动态规划的解法有两种:一种是使用二维dp数组,另一种是使用一维dp数组
      • 二维dp数组:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少
      • 一维dp数组:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]
  • 01背包问题的重要性:
    • 背包问题的理论基础重中之重是01背包,一定要理解透
    • 01背包和完全背包的区别是:01背包的每个物品数量只有一个,而完全背包每个物品的数量有无数个
  • 还有其他的背包问题:
    • 01背包:有n种物品,每种物品只有一个
    • 完全背包:有n种物品,每种物品有无限个
    • 多重背包:有n种物品,没有物品的个数各不相同

二维dp数组

对于动态规划的思路:一定要时刻记着5个步骤:1、确定dp数组以及下标的含义;2、确定递推公式;3、dp数组如何初始化;4、确定遍历顺序;5、打印dp数组

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

        二维dp数组是基础,在此基础上可以优化为一维dp数组,所以要先弄清楚二维dp数组

        dp[i][j]:  背包中物品的总价值
        i 代表:物品遍历到物品 i
        j 代表:目前背包的容量

2、确定递推公式

        要求递推公式,就是要求,当前背包中的最大值该怎么获得
        有两个方向:

  • 由dp[i - 1][j]推出:这种情况代表,当前物品 i 放不到背包中,只能获取前 i-1 个物品放入后,所能得到的最大价值
  • 由dp[i - 1][j - wight[i]]推出:这种情况代表:当前的物品 i 可以放到背包中,物品 i 的价值为 value[i] , 此时还需要知道 放了物品 i 之后,所剩容量( j - wight[i] ) 还能放的最大价值为 ( dp[i - 1][j - wight[i]] ) ,两者之和就是遍历到当前物品,容量为j的背包中可以存放的最大价值
  • 当前背包的最大容量就这两种情况,所以在这两种情况中选取最大值就可以
  • 最终的递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);                    

3、dp数组如何初始化

        初始化dp数组的时候,一定要和dp数组的定义吻合

        首先,当背包容量为0时,不论遍历到哪个物品,背包中的最大价值都是0,即dp[i][0] 全为0 

        其次,从递推公式中可以知道,dp[i][j] 的两种状态都是由 dp[i - 1][j] 来获取的,所以 dp[0][j]的状态一定要进行初始化,因为0-1为 -1,这种情况出界了

         最后,是除左边界和右边界的所有情况的初始化,这些位置其实赋值任何都可以,因为在后面填充数组的过程中,其中的值还是会被 递推公式中的值所覆盖,跟本身初始化是什么值无关

4、确定遍历顺序

这里遍历右两种情况:

  • 先遍历物品,再遍历背包重量
  • 先遍历背包重量,再遍历物品

这两种情况都是可以的,但是先遍历物品更好理解

因为dp[i][j] 每次都是获取正上方或者左上角的数据,不会被右方的数据影响

5、打印dp数组

表格中的最后一个元素就是需要求的结果

 最好根据递推公式手动推导一下这个表格就很清楚了

最终代码

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

    /**
     * 动态规划获得结果
     * @param weight  物品的重量
     * @param value   物品的价值
     * @param bagSize 背包的容量
     */
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

        // 创建dp数组
        int goods = weight.length;  // 获取物品的数量
        int[][] dp = new int[goods][bagSize + 1];

        // 初始化dp数组
        // 创建数组后,其中默认的值就是0
        for (int j = weight[0]; j <= bagSize; j++) {
            dp[0][j] = value[0];
        }

        // 填充dp数组
        for (int i = 1; i < weight.length; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i]) {
                    /**
                     * 当前背包的容量都没有当前物品i大的时候,是不放物品i的
                     * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
                     */
                    dp[i][j] = dp[i-1][j];
                } else {
                    /**
                     * 当前背包的容量可以放下物品i
                     * 那么此时分两种情况:
                     *    1、不放物品i
                     *    2、放物品i
                     * 比较这两种情况下,哪种背包中物品的最大价值最大
                     */
                    dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
                }
            }
        }

        // 打印dp数组
        for (int i = 0; i < goods; i++) {
            for (int j = 0; j <= bagSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println("\n");
        }
    }
}

一维dp数组

         一维dp数组是对二维dp数组情况的优化,因为dp[i][j] 是在dp[i - 1][j] 的基础上得到的,所以完全可以只维护一维dp数组,可以理解成是一个滚动数组,每遍历一个物品,一维dp数组全部更新一次

1、确定dp数组的定义

        一维数组:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]

2、确定递推公式

        根据前面二维dp数组的递推公式来推导一下,一维dp数组的递推公式,还是两种情况:

  • 由dp[j]推出,这种情况代表没有放物品 i ,所以还是原来的值,代表放入容量为 j 的前 i-1 个物品的最大价值
  • 由dp[j - weight[i]],这种情况代表放了物品 i ,物品i 的价值是 value[i] , 剩余的容量(j - weigth[i])能放的 i - 1 个物品的价值为 dp[j - weight[i]],所以放物品 i 总的价值为 dp[j - weight[i]] + value[i]
  • 所以 dp[j] = max(dp[j] ,  dp[j - weight[i]] + value[i]);

3、初始化dp数组

        对于dp[0] ,也就是 背包容量为 0 的时候,肯定应该赋值为 0 ,即 dp[0] = 0

        但是对于非0下标应该怎么赋值呢,其实dp数组在推导的过程中一定是取最大数的,如果题目给的价值都是正整数,那么非0下标都初始化0就可以了

        这样才能让dp数组在递推公式的过程中取的最大价值,而不是被覆盖(比如初始化100 就会被覆盖)

        所以假设物品的价值都是大于0的,所以dp数组初始化的时候,都初始化成0 就可以了

4、遍历顺序

        这里的遍历顺序是从后向前进行遍历,因为dp[j - weight[i]]这种情况要用到上一个物品对应的数组前面的值

        这种倒序遍历保证了物品 i 只被放入一次,如果从前向后遍历会造成物品被多次加入背包的情况

        要清楚的是:
        二维dp数组的时候是有两种写法的,一种是先遍历物品再遍历背包;另一种是先遍历背包再遍历物品,而且从前向后遍历,或者从后向前遍历都是可以的
        一维dp数组是根据二维dp数组中的先遍历物品再遍历背包推导来的,而且每次获取下一个物品对应的dp[i][j]时会根据前面的dp[i-1][j-weight[i]]获得,这个数组是在当前层的左上方,所以当是一维数组的时候,移动要从后向前进行遍历,前面的数在这一层更新中还要继续使用呢

        并且如果在一维数组中采用从前向后的遍历时,会导致物品被多次添加进背包,因为在确定下标大的元素的时候会用到下标小的元素,所以下标小的元素要最后才更新,所以要从后向前遍历

        二维dp数组中,当前层是根据上一层来推导出来的,当前层的每一个值不受上一层的值得影响,两层得数据是完全隔离开来的,所以正序遍历和倒序遍历都可以
        一维dp数组中,数组的内存空间是重复利用的,如果正序遍历,当前计算出来的值就会和旧值产生冲突

5、打印dp数组

最终代码

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

    /**
     * 动态规划获得结果
     * @param weight  物品的重量
     * @param value   物品的价值
     * @param bagSize 背包的容量
     */
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

        // 创建dp数组
        int[] dp = new int[bagSize + 1];

        // 初始化dp数组
        // 将dp[]数组初始化为0,默认就是,不用操作

        // 遍历填充dp数组
        // 外层循环代表几个物品,循环几次
        // 内层循环更换最大值
        for (int i = 0; i < weight.length; i++) {
            for (int j = bagSize; j >= weight[i]; j-- ) {
                dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
            }

            // 打印dp数组
            System.out.print("物品" + i + "\t");
            for (int j = 0; j <= bagSize; j++) {

                System.out.print(dp[j] + "\t");
            }
            System.out.println("\n");
        }

    }
}

416. 分割等和子集 

题目链接:力扣

思路

        这道题目是可以使用回溯法的,只要找到集合中能够出现sum/2的子集总和,那就算分割成两个相同元素和子集了

        但是使用回溯法会超时,直接使用动态规划,使用背包问题解决

        既然是背包问题,先要判断是什么样的背包问题,如果其中的每个物品只能使用一次,那就是01背包,如果每个物品可以使用无数次,那就是完全背包

        显然这道题目是01背包

        既然是01背包问题,那我们需要明确,物品、物品重量、物品价值、背包容量

        物品:数组中的元素
        物品重量:元素大小
        物品价值:元素大小
        背包容量:数组和 / 2

分割等和子集

1、确定dp[]数组的含义

        使用一维dp[]数组,dp[j] 代表:当 数组和/2 为 j 时,数组中的某些元素相加可以 数组和/2

2、递推公式

        两个方法可以获取:

  • dp[j]
  • dp[j - nums[i]] + nums[i]

        所以递推公式为 dp[j] = max(dp[j],dp[j - nums[i]] + nums[i]);

3、初始化

        只包含正整数,最小值为0,所以初始值默认为0

4、遍历顺序

        因为这一轮数组的值是从上一轮的前方和获取的,所以需要从后向前遍历

最终代码

class Solution {
    public boolean canPartition(int[] nums) {

        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        if ( sum % 2 != 0) {
            return false;
        }

        // 目标和为
        int target = sum / 2;

        // 创建dp[j]数组
        int[] dp = new int[target + 1];

        // 填充dp数组
        for (int i = 0; i < nums.length; i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] = Math.max(dp[j],dp[j-nums[i]] + nums[i]);
            }
        }

        return target == dp[target];

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一次搞懂SpringBoot核心原理:自动配置、事件驱动、Condition

前言 SpringBoot是Spring的包装&#xff0c;通过自动配置使得SpringBoot可以做到开箱即用&#xff0c;上手成本非常低&#xff0c;但是学习其实现原理的成本大大增加&#xff0c;需要先了解熟悉Spring原理。如果还不清楚Spring原理的&#xff0c;可以先查看博主之前的文章&…

Vue实现简易购物车功能

用Vue写一个列表案例&#xff0c;页面布局什么的dom&#xff0c;不需要自己事先全部排好&#xff0c;而是通过li遍历&#xff0c;把数据遍历出来&#xff1b;先定义好div标签&#xff0c;li根据数组的长度datalist进行遍历&#xff0c;图片的链接要用“&#xff1a;”&#xff…

算法设计与分析 SCAU8597 石子划分问题

8597 石子划分问题 时间限制:1000MS 代码长度限制:10KB 提交次数:0 通过次数:0 题型: 编程题 语言: G;GCC;VC;JAVA Description 给定n个石子&#xff0c;其重量分别为a1,a2,a3,…,an。 要求将其划分为m份&#xff0c;每一份的划分费用定义为这份石子中最大重量与最小重量差…

nRF52832闪存FDS使用(SDK17.1.0)

陈拓 2022/10/29-2022/11/22 1. 简介 对于Nordic芯片内部FLASH存储管理有两种方式&#xff0c;FS (Flash Storage)和FDS (Flash Data Storage) 。FS是FDS的底层实现&#xff0c;FDS是对FS的封装&#xff0c;使用更容易。 Flash Data Storage&#xff08;FDS&#xff09;模块是…

容器与容器编排系统

Docker公司发明的「容器镜像」技术&#xff0c;创造性地解决了应用打包的难题。改变了一大批诸如容器编排、服务网格和云原生等技术&#xff0c;深刻影响了云计算领域的技术方向。 一、Docker 容器技术 概括起来&#xff0c;Docker 容器技术有3个核心概念容器、镜像和镜像仓库…

当3A射击游戏遇上Play to Earn,暴躁兔带你了解MetalCore

MetalCore是一款具有机甲风格的战斗射击类的Play to Earn & Free to Play游戏&#xff0c;暴躁兔对这款游戏之前也有做过分析&#xff0c;MetalCore在近期启动了alpha开放世界测试&#xff0c;之前有NFT的玩家获得key code之后可以在PC端下载后进行体验。alpha阶段在10月20…

如何使IOT2050成为PN设备

Profinet Driver&#xff08;PNDriver&#xff09;从V2.3开始支持IO设备(IOD)功能&#xff0c;支持通用网络接口和Linux操作系统&#xff0c;最小支持2ms的通讯周期。本文介绍如何编译PNDriver并运行在IOT2050上。 1. 编译PNDriver 因为PNDriver只支持32位模式&#xff0c;因…

TiDB ——TiKV

TiDB ——TiKV TiKV持久化 TiKV架构和作用TiKV数据持久化和读取TiKV如何提供MVCC和分布式事务支持TiKV基于Raft算法的分布式一致性TiKV的coprocessor TiKV架构和作用 数据持久化分布式一致性MVCC分步式事务Coprocessor RocksDB 单机持久化引擎&#xff0c;单机key-value的…

L2十档行情API接口的开发原理是什么?

L2十档行情API接口的开发原理不知道大家有没有了解过&#xff0c;其实在现实的股市量化交易中&#xff0c;就有不少的投资者也在思考这个问题&#xff0c;并且也有的部分交易者会选择自己开发来使用&#xff0c;不仅支持A股所有的股票数据&#xff0c;也能对期货、外汇、黄金等…

个人项目-部署手册

前言 一、RDS和ECS购买与配置 https://www.aliyun.com/?spm5176.12818093.top-nav.dlogo.3be916d0u0Ncp9 购买RDS(MYSQL)和ECS(规格族&#xff1a;突发性能实例 t6 )的时候尽量选择一个大区》如&#xff1a;华东&#xff08;杭州&#xff09;配置不需要太高(够自己使用就行了…

干货分享 | B站SLO由失败转成功,B站SRE做对了什么?

最近几年&#xff0c;Google SRE在国内非常流行。 Google SRE方法论中提出了SLO是SRE实践的核心&#xff0c;SLO为服务可靠性设定了一个目标级别&#xff0c;它是量化线上质量的关键因素&#xff0c;它是用来回答一个服务到底“什么时候叫做挂了”的根本依据&#xff0c;也是可…

Python网络爬虫入门篇

1. 预备知识 学习者需要预先掌握Python的数字类型、字符串类型、分支、循环、函数、列表类型、字典类型、文件和第三方库使用等概念和编程方法。 2. Python爬虫基本流程 a. 发送请求 使用http库向目标站点发起请求&#xff0c;即发送一个Request&#xff0c;Request包含&am…

xxl-job 执行成功,但是报“任务结果丢失,标记失败“错误

问题1:使用xxl定时更新数据,发现执行结果是失败的 打开日志查看,发现没报错,结果是200 打开备注,上面写着"结果丢失". 再仔细对比下,发现外面日志列表中的执行时间是00:20:18;而日志记录中的最后时间是00:39:32;也就是说线程还没执行完,就先报结果错误了. 对比日志时…

[附源码]Python计算机毕业设计宠物寄养管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Jmeter压力测试教程(上)

JMeter压力测试一、 简介1.1优点1.2缺点二、安装2.1下载2.2解决中文乱码问题2.5配置环境变量2.4启动入门案例三、线程组相关3.1 创建多个线程组3.2 并发和顺序执行3.3 两个特殊的线程组&#xff08;setUp/tearDown&#xff09;线程细节设置默认http请求新增接口信息头管理器四、…

SAP ADM100-1.2之系统登录过程(ABAP)

1、SAP登录过程 为了在前端最终用户和SAP系统实例之间创建连接,sapgui.exe程序需要启动参数。参数字符串是由saplogon .exe程序使用为登录选择的SAP GUI的信息创建。 SAP登录信息有以下两个来源:SAP Logon的配置文件,以及对所选系统的消息服务器的直接请求(下图中的步骤1和…

使用Go+Lua解决Redis秒杀中库存与超卖问题

1、简介 Go语言连接go-redis进行数据库的连接&#xff0c;如果你对这部分尚不了解&#xff0c;建议你先学习这部分知识。另外&#xff0c;本秒杀主要解决两个问题&#xff0c;第一个就是超卖问题&#xff0c;另一个就是库存问题。没有设计专门的页面来模拟并发&#xff0c;我们…

布谷蓝途:易知微「可视大脑助力智慧教育」主题分享精彩实录

如今&#xff0c;大数据技术在教育领域的应用与普及正驶入“快车道”&#xff0c;但仍然存在资源管理分散、数据各自为阵、运营模式传统等痛点&#xff0c;如何借助新技术、新机遇并充分发挥大数据在教育教学中的支撑作用成为重中之重。 布谷蓝途作为国内前沿的大数据方案与服…

网分测试线缆怎么选?

如何在众多选择中寻找到最佳的测试电缆?以下内容由普科科技PRBTEK整理&#xff0c;以下内容将阐述电缆与电缆组件的机械及电气性能&#xff0c;以及如何选择您理想的测试电缆。 2004年5月&#xff0c;美国时代微波系统公司的测试工程师对50欧姆测试电缆的要求作出以下概述&…

银河麒麟桌面操作系统V10安装过程

文章目录下载镜像导入VMware启动安装下载镜像导入VMware 首先去麒麟生态网站注册登录&#xff0c;找到适合自己版本的操作系统 打开VMware新建虚拟机 把镜像放进来 选择Liunx的ubuntu版本 分配处理器和内核 分配内存 后面的就网络、I/O、硬盘按照默认配置就行 启动安装 …