【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题

在这里插入图片描述

本篇博客给大家带来的是01背包问题之动态规划解法技巧.
🐎文章专栏: 动态规划
🚀若有问题 评论区见
欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

王子,公主请阅🚀

  • 要开心
    • 要快乐
      • 顺便进步
  • 1. 01背包
  • 2. 分割等和子集

要开心

要快乐

顺便进步

1. 01背包

题目链接: DP41 【模板】01背包

题目内容:
描述
在这里插入图片描述

示例1
输入:
3 5
2 10
4 5
1 4

输出:
14
9

说明:
装第一个和第三个物品时总价值最大,但是装第二个和第三个物品可以使得背包恰好装满且总价值最大。
示例2
输入:
3 8
12 6
11 8
6 8

输出:
8
0

说明:
装第三个物品时总价值最大但是不满,装满背包无解。
备注:
要求O(nV)的时间复杂度,O(V)空间复杂度

第一Ⅰ问的动态规划

1. 状态表示
在这里插入图片描述

01背包问题本质上还是 线性dp问题,按线性dp来定义状态即可.
第Ⅰ问: 尝试一维定义
dp[i] 表示在前 i 个位置中选择物品, 在所有的选法中, 物品总体积不超过背包容量V 时的最大价值.
上述一维定义在写状态转移方程时不能保证所选物品体积不会超过背包的容量,并且选完之后背包剩余容量也时未知的.一维定义解决不了问题,
用二维定义dp[i][j] 表示在前 i 个位置中选择物品, 物品总体积不超过 V的所有选法中能选出来的最大价值.

2. 状态转移方程
根据最后一个位置是否选择, 来划分问题:
① i 物品不选, dp[i] = dp[i-1][j]
② i 物品要选, 需要保证剩余容量大于等于0, j-v[i]>= 0, w[i] + dp[i-1][j-v[i]];

3. 初始化
选择物品是从下标1 开始的, 到n结束, 那么dp表就需要多创建一行一列,处理两个细节问题:
①下标之间的对应关系: i -> i j -> j
②初始化虚拟节点:
第一行表示的是 物品可选,总体积不超过 j 的最大价值. 既然没有物品,那就不选,全都为0.
第一列表示的是 所选物品总体积不超过0的最大价值. 此时最大价值就是0,不选即可,全都为0.

在这里插入图片描述

4. 填表顺序
根据状态转移方程可知,要想得到dp[i][j] 就得先知道dp[i-1][j]或者dp[i-1][j-v[i]],
所以应该从上往下填写每一行
从左往右填写每一列.

5. 返回值
打印 dp[n][V]即可.

第二 Ⅱ问的动态规划

1. 状态表示
在这里插入图片描述

与第Ⅰ问同样的分析方法:
二维定义dp[i][j] 表示在前 i 个位置中选择物品, 物品总体积刚好为 V的所有选法中能选出来的最大价值.
可能存在所有选法都不能恰好装满背包,我们规定此时dp[i][j] = -1;

2. 状态转移方程
根据最后一个位置是否选择, 来划分问题:
① i 物品不选, dp[i] = dp[i-1][j]
② i 物品要选, 需要保证剩余容量大于等于0, j-v[i]>= 0, w[i] + dp[i-1][j-v[i]];

3. 初始化
选择物品是从下标1 开始的, 到n结束, 那么dp表就需要多创建一行一列,处理两个细节问题:
①下标之间的对应关系: i -> i j -> j
②初始化虚拟节点:
第一行表示的是 没有物品可选,总体积等于 j 的最大价值.dp[0][0] = 0, 后面 无论选不选,总价值都达不到 j ,于是全都初始化为-1.
第一列表示的是 所选物品总体积等于0的最大价值. 此时最大价值就是0,不选即可,全都为0.

在这里插入图片描述

4. 填表顺序
根据状态转移方程可知,要想得到dp[i][j] 就得先知道dp[i-1][j]或者dp[i-1][j-v[i]],
所以应该从上往下填写每一行
从左往右填写每一列.

5. 返回值
打印 dp[n][V]即可.

第三 优化

①利用滚动数组做空间上的优化
在这里插入图片描述

在这里插入图片描述
②直接在原始代码上修改
Ⅰ删除所有的 i 维
(★)Ⅱ 填表时从右往左遍历 j .

第四 代码实现

// //优化前:
        //  Scanner in = new Scanner(System.in);
        // int N = 1010;
        // // 注意 hasNext 和 hasNextLine 的区别
        // int n = in.nextInt();
        // int V = in.nextInt();
        // int[][] dp1 = new int[N][N];
        // int[][] dp2 = new int[N][N];
        // int[] v = new int[N];
        // int[] w = new int[N];
        // for (int i = 1; i <= n; ++i) {
        //     v[i] = in.nextInt();
        //     w[i] = in.nextInt();
        // }
        // //1.解决第一个问题(1)求这个背包至多能装多大价值的物品?
        // //填表
        // for (int i = 1; i <= n; ++i) {
        //     for (int j = 0; j <= V; ++j) { //修改遍历顺序
        //      dp1[i][j] = dp1[i-1][j];
        //      if(j >= v[i]) {
        //         dp1[i][j] = Math.max(dp1[i][j], w[i] + dp1[i-1][j - v[i]]);
        //      }
        //     }
        // }
        // System.out.println(dp1[n][V]);
        // //2. 解决第二个问题 (2)若背包恰好装满,求至多能装多大价值的物品?
        // for (int i = 1; i <= V; ++i) {
        //     dp2[0][i] = -1;
        // }
        // for (int i = 1; i <= n; ++i) {
        //     for (int j = 0; j <= V; ++j) {
        //         dp2[i][j] = dp2[i-1][j];
        //         if (j-v[i] >= 0 && dp2[i-1][j - v[i]] != -1) {
        //             dp2[i][j] = Math.max(dp2[i][j], w[i] + dp2[i-1][j - v[i]]);
        //         }
        //     }
        // }
        // //若是-1则输出0, 若不是则正常输出.
        // System.out.println((dp2[n][V] == -1 ? 0 : dp2[n][V]));

        //优化后:
        Scanner in = new Scanner(System.in);
        int N = 1010;
        // 注意 hasNext 和 hasNextLine 的区别
        int n = in.nextInt();
        int V = in.nextInt();
        int[] dp1 = new int[N];
        int[] dp2 = new int[N];
        int[] v = new int[N];
        int[] w = new int[N];
        for (int i = 1; i <= n; ++i) {
            v[i] = in.nextInt();
            w[i] = in.nextInt();
        }
        //1.解决第一个问题(1)求这个背包至多能装多大价值的物品?
        //填表
        for (int i = 1; i <= n; ++i) {
            for (int j = V; j >= v[i]; --j) { //修改遍历顺序
                dp1[j] = Math.max(dp1[j], w[i] + dp1[j - v[i]]);
                
            }
        }
        System.out.println(dp1[V]);
        //2. 解决第二个问题 (2)若背包恰好装满,求至多能装多大价值的物品?
        for (int i = 1; i <= V; ++i) {
            dp2[i] = -1;
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = V; j >= v[i]; --j) {
                if (dp2[j - v[i]] != -1) {
                    dp2[j] = Math.max(dp2[j], w[i] + dp2[j - v[i]]);
                }
            }
        }
        //若是-1则输出0, 若不是则正常输出.
        System.out.println((dp2[V] == -1 ? 0 : dp2[V]));

2. 分割等和子集

题目链接: 416. 分割等和子集

题目内容:
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

1 <= nums.length <= 200
1 <= nums[i] <= 100

第一 预处理

直接去判断两个子集的元素和是否相等会有点复杂, 可以转化为只研究一个子集等于原数组元素和的一半, 即在数组中选择一些数, 这些数的和是否等于原数组元素和的一半.

第二 动态规划

1. 状态表示
根据第一题模板题可知定义状态的方式:
dp[i][j]表示在nums的前 i 个位置中选择一些数,所有选法中,是否存在一些数,使得这些数之和为 j.

2. 状态转移方程
根据最后一个位置nums[i]是否选择来划分情况:
①nums[i]不选: dp[i][j] = dp[i-1][j];
②nums[i]要选: 需要保证所选数之和不超过 j . j - nums[i] >= 0, dp[i][j-nums[i]];

3. 初始化
dp表多创建一行一列,需要处理两个问题:
①dp表与nums表元素之间的对应关系:
i -> i-1; j -> j-1
②初始化虚拟节点(就是多创建的一行一列),保证填表的正确性:

第一行 i=0意味着没有元素可选,怎么选最大值都只能是0,所以dp[0][0] = true,其余全部初始化为false.
第一列 j=0意味着需要凑成和为0, 直接不选即可. 故从i=1开始全部初始化为true.

在这里插入图片描述

4. 填表顺序
根据状态转移方程可知,要想求得dp[i][j]就得先知道dp[i-1][j]和dp[i-1][j-nums[i]],所以可知填表顺序为:
从上往下填写每一行
每一行从左往右填写

5. 返回值
返回dp[nums.length][原数组元素和的一半]

第三 优化

①利用滚动数组做空间上的优化
在这里插入图片描述

在这里插入图片描述
②直接在原始代码上修改
Ⅰ删除所有的 i 维
(★)Ⅱ 填表时从右往左遍历 j .

第四 代码实现

class Solution {
    public boolean canPartition(int[] nums) {
    //     int n = nums.length;
    //     int sum = 0;
    //     for(int x : nums) sum += x;
    //     if(sum%2 == 1) return false;
    //     int aim = sum/2;
    //     boolean[][] dp = new boolean[n+1][aim+1];
    //     for(int i = 0;i <= n;++i) {
    //         dp[i][0] = true;
    //     }
    
    //     for(int i = 1;i <= n;++i) {
    //         if(i < n) {
    //             sum += nums[i];
    //         }
    //         for(int j = 1;j <= aim;++j) {
    //             dp[i][j] = dp[i-1][j];
    //             if(j >= nums[i-1] && dp[i][j] == false) {
    //                 dp[i][j] = dp[i-1][j-nums[i-1]];
    //             }
    //         }
    //     }
    //     return dp[n][aim];
    // }

    //优化: 删除第一维, 从右往左遍历第二维
    int n = nums.length;
        int sum = 0;
        for(int x : nums) sum += x;
        if(sum%2 == 1) return false;
        int aim = sum/2;
        boolean[] dp = new boolean[aim+1];
        dp[0] = true;
    
        for(int i = 1;i <= n;++i) {
            if(i < n) {
                sum += nums[i];
            }
            for(int j = aim;j >= nums[i-1];--j) { //修改遍历顺序,本质上是滚动数组的优化.
                
                if(dp[j] == false) {
                    dp[j] = dp[j-nums[i-1]];
                }
            }
        }
        return dp[aim];
    }
}

本篇博客到这里就结束啦, 感谢观看 ❤❤❤

🐎期待与你的下一次相遇😊😊😊

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游戏steam_api64.dll文件缺失怎么办?无法找到指定的模块的解决方法

在使用Steam平台运行游戏时&#xff0c;有时会遇到“steam_api64.dll文件缺失&#xff0c;无法找到指定的模块”的错误提示。这个问题通常是由于该文件被误删、病毒感染、系统更新不兼容或游戏安装不完整等原因造成的。以下是一些有效的解决方法&#xff0c;帮助你解决steam_ap…

Linux学习笔记——网络管理命令

一、网络基础知识 TCP/IP四层模型 以太网地址&#xff08;MAC地址&#xff09;&#xff1a; 段16进制数据 IP地址&#xff1a; 子网掩码&#xff1a; 二、接口管命令 ip命令&#xff1a;字符终端&#xff0c;立即生效&#xff0c;重启配置会丢失 nmcli命令&#xff1a;字符…

在 Windows 系统上,将 Ubuntu 从 C 盘 迁移到 D 盘

在 Windows 系统上&#xff0c;如果你使用的是 WSL&#xff08;Windows Subsystem for Linux&#xff09;并安装了 Ubuntu&#xff0c;你可以将 Ubuntu 从 C 盘 迁移到 D 盘。迁移过程涉及导出当前的 Ubuntu 发行版&#xff0c;然后将其导入到 D 盘的目标目录。以下是详细的步骤…

simulink入门学习01

文章目录 1.基本学习方法2.图形环境--模块和参数3.激活菜单---添加到模型3.1输入选项3.2添加到模型3.3更改运算3.4验证要求 4.乘以特定值--Gain模块4.1引入gain模块4.2更改增益参数4.3接入系统4.4大胆尝试 1.基本学习方法 今天突然想要学习这个simulink的相关知识&#xff0c;…

Linux的基本指令(上)

1.ls指令 语法&#xff1a;ls [选项] [目录或文件] 功能&#xff1a;对于⽬录&#xff0c;该命令列出该⽬录下的所有⼦⽬录与⽂件。对于⽂件&#xff0c;将列出⽂件名以及其他信息。 常用选项&#xff1a; -a 列出⽬录下的所有⽂件&#xff0c;包括以 . 开头的隐含⽂件。 -d 将…

一文详解Filter类源码和应用

背景 在日常开发中&#xff0c;经常会有需要统一对请求做一些处理&#xff0c;常见的比如记录日志、权限安全控制、响应处理等。此时&#xff0c;ServletApi中的Filter类&#xff0c;就可以很方便的实现上述效果。 Filter类 是一个接口&#xff0c;属于 Java Servlet API 的一部…

【算法】数论基础——唯一分解定理(算术基本定理)python

目录 定义进入正题热身训练实战演练扩展衍生判断一个数是否为完全平方数举一反三总结 定义 唯一分解定理&#xff1a;也叫做算数基本定理: 任意一个大于1的整数N&#xff0c;都可以唯一分解为若干个质数的乘积 换句话说&#xff0c;任何大于1的整数n可以表示为&#xff1a; 例如…

互联网医院成品|互联网医院开发|互联网医院搭建

在数字化医疗蓬勃发展的当下&#xff0c;互联网医院系统已成为医疗服务体系中至关重要的组成部分。它打破了传统医疗服务在时间和空间上的限制&#xff0c;为患者提供了更加便捷、高效的医疗服务。而一套完善的互联网医院系统&#xff0c;有几个功能是不能缺少的。 在线问诊功能…

Go的内存逃逸

Go的内存逃逸 内存逃逸是 Go 语言中一个重要的概念&#xff0c;指的是本应分配在栈上的变量被分配到了堆上。栈上的变量在函数结束后会自动回收&#xff0c;而堆上的变量需要通过垃圾回收&#xff08;GC&#xff09;来管理&#xff0c;因此内存逃逸会增加 GC 的压力&#xff0…

填充每个节点的下一个右侧节点指针力扣--116,117

目录 题目 思路 代码 题目 116 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node { int val; Node *left; Node *right; Node *next; } 填充它的每个 next 指针&#xff0c…

钉钉群机器人设置——python版本

钉钉群机器人设置——python版本 应用场景钉钉界面操作程序开发效果展示 应用场景 由于工作需要&#xff0c;很多项目执行程序后出现报错信息无法第一时间收到&#xff0c;因此实时预警对于监控程序还是有必要。&#xff08;仅个人观点&#xff09; 参考文档及博客&#xff1a…

初步认识操作系统(Operator System)

目录 一、概念二、设计OS的目的三、定位四、操作系统上下的分级五、如何理解 "管理"六、总结 一、概念 任何计算机系统都包含一个基本的程序集合&#xff0c;称为操作系统(OS)。操作系统包括&#xff1a; 内核&#xff08;进程管理&#xff0c;内存管理&#xff0c…

文明6mod发布并开源:更多的蛮族营地扫荡收益mod

更多的蛮族营地扫荡收益mod&#xff08;More_Barbarian_Camp_RAID_luke&#xff09; 效果为&#xff1a; 更多的蛮族营地扫荡收益&#xff0c;增加到100金币&#xff0c;适用于野蛮氏族模式 原版本的扫荡收益非常鸡肋~&#xff01; mod下载链接&#xff1a; https://downlo…

社区养老服务平台的设计与实现(代码+数据库+LW)

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#…

websocket实现

由于安卓资源管理器展示的路径不尽相同,各种软件保存文件的位置也不一定一样.对于普通用户上传文件时,查找文件可能是一个麻烦的事情.后来想到了一个办法,使用pc端进行辅助上传. 文章目录 实现思路1.0 实现定义web与客户端通信数据类型和数据格式web端websocket实现web端对客户…

(一)HTTP协议 :请求与响应

前言 爬虫需要基础知识&#xff0c;HTTP协议只是个开始&#xff0c;除此之外还有很多&#xff0c;我们慢慢来记录。 今天的HTTP协议&#xff0c;会有助于我们更好的了解网络。 一、什么是HTTP协议 &#xff08;1&#xff09;定义 HTTP&#xff08;超文本传输协议&#xff…

arcgis短整型变为长整型的处理方式

1.用QGIS的重构字段工具进行修改&#xff0c;亲测比arcgis的更改字段工具有用 2.更换低版本的arcgis10.2.2&#xff0c;亲测10.5和10.6都有这个毛病&#xff0c;虽然官方文档里面说的是10.6.1及以上 Arcgis10.2.2百度链接&#xff1a;https://pan.baidu.com/s/1HYTwgnBJsBug…

从音频到 PDF:AI 全流程打造完美英文绘本教案

今天把英文绘本的自学教案自动生成流程完成了&#xff0c;我分享一下整个实现思路&#xff0c;让你也轻松搞定英文绘本教案的产出&#xff0c;让孩子的学习之路更加顺畅。  从音频到 PDF&#xff1a;AI 全流程打造完美英文绘本教案 一、音频转文本&#xff1a;AI 助力第一步 …

C++ Qt练习项目 日期时间数据

个人学习笔记 代码仓库 GitCode - 全球开发者的开源社区,开源代码托管平台 新建项目 设计UI 实现组件功能 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }…

HarmonyOS DevEco Studio模拟器点击运行没有反应的解决方法

HarmonyOS DevEco Studio模拟器点击运行没有反应的解决方法 翻遍了CSDN&#xff0c;试了所有办法都没办法&#xff0c;最后偶然间竟然解决了 解决方法其实很简单&#xff1a;本地模拟器下载路径下面不能有中文。。。。。 切换正确路径以后&#xff0c;成功运行&#xff0c;哦…