背包问题双雄:01 背包与完全背包详解(Java 实现)

news2025/6/10 12:36:13

一、背包问题概述

背包问题是动态规划领域的经典问题,其核心在于如何在有限容量的背包中选择物品,使得总价值最大化。根据物品选择规则的不同,主要分为两类:

  • 01 背包:每件物品最多选 1 次(选或不选)。
  • 完全背包:每件物品可选无限次。

本文将深入解析两者的核心逻辑、状态转移及优化技巧,并通过 Java 代码实现典型场景。

二、01 背包问题:选或不选的博弈

问题描述

给定背包容量 W 和 N 个物品(每个物品重量 w[i]、价值 v[i]),每个物品最多选 1 次,求背包能承载的最大价值。

核心思路

1. 状态定义
  • dp[j]:背包容量为 j 时能获得的最大价值。
2. 状态转移方程
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
  • 选第 i 件物品:需确保容量 j >= w[i],价值为前 i-1 件物品装入容量 j-w[i] 的背包价值 + 当前物品价值。
  • 不选第 i 件物品:价值与前 i-1 件物品装入容量 j 的背包价值相同。
3. 关键实现细节
  • 倒序遍历容量:从 W 到 w[i] 倒序枚举,避免重复选择同一物品。
  • 空间优化:使用一维数组代替二维数组,降低空间复杂度至 O(W)

示例分析

场景:背包容量 W=4,物品列表 [(w=2, v=3), (w=1, v=2), (w=3, v=4)]
二维 DP 表演变

容量 \ 物品0(无)物品 1 (2,3)物品 2 (1,2)物品 3 (3,4)
00000
10022
20333
30355
40355

一维优化 Java 代码

public class Knapsack01 {
    public static int solve(int[] w, int[] v, int W) {
        int N = w.length;
        int[] dp = new int[W + 1]; // dp[j]表示容量j的最大价值

        for (int i = 0; i < N; i++) { // 遍历每个物品
            for (int j = W; j >= w[i]; j--) { // 倒序遍历容量,避免重复选
                dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
        return dp[W];
    }

    public static void main(String[] args) {
        int[] w = {2, 1, 3};
        int[] v = {3, 2, 4};
        int W = 4;
        System.out.println("01背包最大价值:" + solve(w, v, W)); // 输出:5
    }
}

三、完全背包问题:无限选择的智慧

问题描述

与 01 背包不同,完全背包允许每件物品选无限次,求背包能承载的最大价值。

核心思路

1. 状态定义

同 01 背包,dp[j] 表示容量为 j 时的最大价值。

2. 状态转移方程
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
  • 允许重复选择:由于每件物品可选多次,需正序遍历容量(从 w[i] 到 W),确保当前物品可被多次选取。
3. 关键实现细节
  • 正序遍历容量:从 w[i] 到 W 正序枚举,允许同一件物品被多次计算。

示例分析

场景:背包容量 W=4,物品列表同 01 背包示例(允许无限选)。
二维 DP 表演变

容量 \ 物品0(无)物品 1 (2,3)物品 2 (1,2)物品 3 (3,4)
00000
10022
20344
30366
40688

一维优化 Java 代码

public class KnapsackComplete {
    public static int solve(int[] w, int[] v, int W) {
        int N = w.length;
        int[] dp = new int[W + 1];

        for (int i = 0; i < N; i++) { // 遍历每个物品
            for (int j = w[i]; j <= W; j++) { // 正序遍历容量,允许重复选
                dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
        return dp[W];
    }

    public static void main(String[] args) {
        int[] w = {2, 1, 3};
        int[] v = {3, 2, 4};
        int W = 4;
        System.out.println("完全背包最大价值:" + solve(w, v, W)); // 输出:8(选4件物品2)
    }
}

四、核心对比:01 背包 vs 完全背包

特性01 背包完全背包
物品选择每个物品最多选 1 次每个物品可选无限次
容量遍历顺序倒序(从 W 到 w [i])正序(从 w [i] 到 W)
状态更新逻辑基于 “旧状态” 避免重复基于 “新状态” 允许重复
时间复杂度O(N×W)O(N×W)
典型场景物品限购、资源分配货币兑换、原料无限供应

五、实战应用:LeetCode 经典题目

1. 01 背包应用:分割等和子集(LeetCode 416)

问题描述:判断数组是否可分割成两个和相等的子集。
思路:转化为 01 背包问题,目标容量为 total/2,判断是否能恰好装满。

public class CanPartition {
    public static boolean canPartition(int[] nums) {
        int total = sum(nums);
        if (total % 2 != 0) return false;
        int target = total / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true; // 容量0时有一种方案(不选任何物品)

        for (int num : nums) {
            for (int j = target; j >= num; j--) { // 倒序遍历防重复
                dp[j] = dp[j] || dp[j - num];
            }
        }
        return dp[target];
    }

    private static int sum(int[] nums) {
        return Arrays.stream(nums).sum();
    }

    public static void main(String[] args) {
        int[] nums = {1, 5, 11, 5};
        System.out.println(canPartition(nums)); // 输出:true(子集和为11)
    }
}

2. 完全背包应用:零钱兑换(LeetCode 322)

问题描述:用最少硬币数组成金额 amount,硬币可重复使用。
思路:转化为完全背包问题,目标是最小化物品数量(硬币数)。

public class CoinChange {
    public static int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0; // 金额0时需0枚硬币

        for (int coin : coins) {
            for (int j = coin; j <= amount; j++) { // 正序遍历允许多选
                if (dp[j - coin] != Integer.MAX_VALUE) {
                    dp[j] = Math.min(dp[j], dp[j - coin] + 1);
                }
            }
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }

    public static void main(String[] args) {
        int[] coins = {1, 2, 5};
        int amount = 11;
        System.out.println(coinChange(coins, amount)); // 输出:3(5+5+1)
    }
}

六、优化技巧与变种问题

1. 空间优化

  • 一维数组代替二维数组,空间复杂度从 O(N×W) 降至 O(W)

2. 多重背包转化

若物品有数量限制(如最多选 k 件),可通过二进制拆分转化为 01 背包问题。例如,最多选 3 件可拆分为 1 件、2 件两个物品。

3. 常见变种

  • 恰好装满背包的方案数:初始化 dp[0] = 1,其余为 0,通过加法原理计算方案数。
  • 二维费用背包:增加一维状态(如重量和体积),状态转移为 dp[j][k] = max(...)

七、总结与学习建议

核心口诀

  • 01 背包:倒序遍历防重复,选或不选取最值。
  • 完全背包:正序遍历允重复,同物多次算价值。

练习推荐

  • 01 背包:LeetCode 494. 目标和
  • 完全背包:LeetCode 518. 零钱兑换 II(求方案数,需正序遍历 + 组合逻辑)

通过对比学习 01 背包与完全背包的核心逻辑,掌握动态规划的状态转移思想,可有效应对背包问题的各类变种。建议结合具体题目反复练习,加深对 “状态定义” 和 “遍历顺序” 的理解。

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

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

相关文章

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …

工厂方法模式和抽象工厂方法模式的battle

1.案例直接上手 在这个案例里面&#xff0c;我们会实现这个普通的工厂方法&#xff0c;并且对比这个普通工厂方法和我们直接创建对象的差别在哪里&#xff0c;为什么需要一个工厂&#xff1a; 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类&#xff1a; 两个发…

鸿蒙Navigation路由导航-基本使用介绍

1. Navigation介绍 Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;Nav…

CMS内容管理系统的设计与实现:多站点模式的实现

在一套内容管理系统中&#xff0c;其实有很多站点&#xff0c;比如企业门户网站&#xff0c;产品手册&#xff0c;知识帮助手册等&#xff0c;因此会需要多个站点&#xff0c;甚至PC、mobile、ipad各有一个站点。 每个站点关联的有站点所在目录及所属的域名。 一、站点表设计…

ZYNQ学习记录FPGA(二)Verilog语言

一、Verilog简介 1.1 HDL&#xff08;Hardware Description language&#xff09; 在解释HDL之前&#xff0c;先来了解一下数字系统设计的流程&#xff1a;逻辑设计 -> 电路实现 -> 系统验证。 逻辑设计又称前端&#xff0c;在这个过程中就需要用到HDL&#xff0c;正文…

Java中HashMap底层原理深度解析:从数据结构到红黑树优化

一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一&#xff0c;是基于哈希表的Map接口非同步实现。它允许使用null键和null值&#xff08;但只能有一个null键&#xff09;&#xff0c;并且不保证映射顺序的恒久不变。与Hashtable相比&#xff0c;Hash…

【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space

问题&#xff1a;IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案&#xff1a;将编译的堆内存增加一点 位置&#xff1a;设置setting-》构建菜单build-》编译器Complier

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统

核心速览 研究背景 ​​研究问题​​&#xff1a;这篇文章要解决的问题是当前大型语言模型&#xff08;LLMs&#xff09;在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色&#xff0c;但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成&#xff08;RA…

【笔记】AI Agent 项目 SUNA 部署 之 Docker 构建记录

#工作记录 构建过程记录 Microsoft Windows [Version 10.0.27871.1000] (c) Microsoft Corporation. All rights reserved.(suna-py3.12) F:\PythonProjects\suna>python setup.py --admin███████╗██╗ ██╗███╗ ██╗ █████╗ ██╔════╝…

五、jmeter脚本参数化

目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …

python基础语法Ⅰ

python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器&#xff0c;来进行一些算术…

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…

EEG-fNIRS联合成像在跨频率耦合研究中的创新应用

摘要 神经影像技术对医学科学产生了深远的影响&#xff0c;推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下&#xff0c;基于神经血管耦合现象的多模态神经影像方法&#xff0c;通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里&#xff0c;本研…

C++中vector类型的介绍和使用

文章目录 一、vector 类型的简介1.1 基本介绍1.2 常见用法示例1.3 常见成员函数简表 二、vector 数据的插入2.1 push_back() —— 在尾部插入一个元素2.2 emplace_back() —— 在尾部“就地”构造对象2.3 insert() —— 在任意位置插入一个或多个元素2.4 emplace() —— 在任意…

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)

漏洞概述 漏洞名称&#xff1a;Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号&#xff1a;CVE-2023-25194 CVSS评分&#xff1a;8.8 影响版本&#xff1a;Apache Kafka 2.3.0 - 3.3.2 修复版本&#xff1a;≥ 3.4.0 漏洞类型&#xff1a;反序列化导致的远程代…

Copilot for Xcode (iOS的 AI辅助编程)

Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot&#xff0c;它能根据上下文补全代码&#xff0c;快速生成常用…

Axure零基础跟我学:展开与收回

亲爱的小伙伴,如有帮助请订阅专栏!跟着老师每课一练,系统学习Axure交互设计课程! Axure产品经理精品视频课https://edu.csdn.net/course/detail/40420 课程主题:Axure菜单展开与收回 课程视频:

RabbitMQ 各类交换机

为什么要用交换机&#xff1f; 交换机用来路由消息。如果直发队列&#xff0c;这个消息就被处理消失了&#xff0c;那别的队列也需要这个消息怎么办&#xff1f;那就要用到交换机 交换机类型 1&#xff0c;fanout&#xff1a;广播 特点 广播所有消息​​&#xff1a;将消息…

高保真组件库:开关

一:制作关状态 拖入一个矩形作为关闭的底色:44 x 22,填充灰色CCCCCC,圆角23,边框宽度0,文本为”关“,右对齐,边距2,2,6,2,文本颜色白色FFFFFF。 拖拽一个椭圆,尺寸18 x 18,边框为0。3. 全选转为动态面板状态1命名为”关“。 二:制作开状态 复制关状态并命名为”开…

未授权访问事件频发,我们应当如何应对?

在当下&#xff0c;数据已成为企业和组织的核心资产&#xff0c;是推动业务发展、决策制定以及创新的关键驱动力。然而&#xff0c;未授权访问这一隐匿的安全威胁&#xff0c;正如同高悬的达摩克利斯之剑&#xff0c;时刻威胁着数据的安全&#xff0c;一旦触发&#xff0c;便可…