面试官:完全背包都不会,是你自己走还是我送你?

别再死记硬背了!用‘无限拿取’的视角,5分钟彻底搞懂完全背包与01背包的核心区别 本文通过生活化类比和代码对比,深入解析完全背包与01背包的核心区别。从物品拿取规则出发,揭示两种背包问题在状态转移方程和循环顺序上的关键差异,并提供经典案例(如零钱兑换和分割等和子集)帮助理解。特别强调完全背包问题的优化实现技巧,帮助读者彻底掌握这两种动态规划经典问题 阅读详情

在如今的面试当中算法题已经成为面试不可或缺的内容,今天就跟大家分享一个还比较困难的笔试题——完全背包。

完全背包问题

有NN种物品和一个容量是 VV的背包,每种物品都有无限件可用。第ii 种物品的体积是 v_ivi​,价值是w_iwi​。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

完全背包问题和01背包的唯一区别就在于物品的个数,在01背包当中所有的物品只有一件,也就只能使用一次。而在完全背包当中物品可以使用无限多次。

比如下面的4个物品,背包能够承受的最大重量为5,我们应该如何选择,使得我们获得的总价值最大:

物品重量价值
A12
B24
C34
D45

这个问题还是比较简单,我们直接从图中看,我们可以选择五个A或者两个B一个A,可以产生最大的收益,最大收益为10。

完全背包问题分析

01背包动态转移方程分析

在01背包问题当中,我们是使用一个二维数组dp[i][j]进行计算,dp[i][j]表示在只使用前i个物品且背包容量为j的情况下,我们能够获得的最大的收益。在这个情况下,我们根据当前背包容量j判断是否能装入第i个物品可以得到下面两个方程(下面公式字母的含义与上文完全背包问题所提到的一致)。

dp[i][j] = \begin{cases} max(dp[i - 1][j - v[i]] + w[i], dp[i - 1][j]), j \ge v[i]\\ dp[i - 1][j] , j \lt v[i] \end{cases}dp[i][j]={max(dp[i−1][j−v[i]]+w[i],dp[i−1][j]),j≥v[i]dp[i−1][j],j<v[i]​

上面01背包的公式的第二条比较简单,如果背包容量不足以容纳第i件物品,那么只能从前i - 1物品当中选择了。我们来仔细分析一下第一条公式。

如果当前背包容量可以容纳第i个物品,那么我们就可以选择第i件物品或者不选择,我们应该选择两种选择当中收益更大的那个。

  • 如果我们不选择第i个物品,那么我们就能够使用容量为j的背包去选择前i - 1个物品,这种情况下我们的最大收益为dp[i - 1][j]
  • 如果选择第i个物品,那么我们背包容量还剩下j - v[i],还可以选择剩下的i - 1个物品,而且我们的收益需要加上w[i],因此我们的收益为dp[i - 1][j - v[i]] + w[i], dp[i - 1][j])

完全背包动态转移方程分析

和01背包问题一样首先对于第i个物品,首先需要判断背包是否能够容纳:

  • 如果背包的容量大于等于第i个物品的体积,那我们就有两种选择:
    • 将第i个物品放入背包当中,但是在这里需要注意的一点是完全背包的物品有无数件,因此当我们选择之后我们的转移方程为dp[i][j - v[i]] + w[i],这里不是i-1而是i,因为第i件物品有无数件。
    • 不将第i个物品放入背包当中,那么我们就能够使用容量为j的背包去选择前i - 1个物品,这种情况下我们的最大收益为dp[i - 1][j]
  • 如果背包容量小于第i件物品的体积,我们就不能够选择第i件物品了,这种情况下我们的最大收益为dp[i - 1][j]

基于上面的分析我们可以知道完全背包问题的动态转移方程为:

dp[i][j] = \begin{cases} max(dp[i][j - v[i]] + w[i], dp[i - 1][j]), j \ge v[i]\\ dp[i - 1][j] , j \lt v[i] \end{cases}dp[i][j]={max(dp[i][j−v[i]]+w[i],dp[i−1][j]),j≥v[i]dp[i−1][j],j<v[i]​

背包问题代码构成设计

根据对动态转移方程的分析,我们可以知道,我们在计算dp[i][j]这个数据的值的时候,我们首先需要将dp[i][j - v[i]]dp[i - 1][j])的结果计算出来,因为dp[i][j]依赖这两个数据。

根据上面的分析和图我们知道,在计算dp[i][j]之前,我们需要将第i行第j列之前的数据和dp[i - 1][j]都计算出来,因为dp[i][j]依赖这些数据。而我们最终需要的结果是dp[N][V]表示在背包容量为V且能够选择前N个物品(也就是所有物品)能够获得的最大的收益,所以我们最终需要求出dp[N][V]的值。

因此基于以上分析,我们要想最终解出dp[N][V]的值,我们可以采取两重for循环,第一重循环遍历物品,第二重循环遍历容量,但是我们的第0行没有前一行,因此我们需要对第0行进行初始化:

// 对第0行进行初始化操作
for (int i = 0; i <= V; ++i) {
    // 如果只能选择第一个数据,而且能选无数次
    // 那就将所有的容量都拿来装第一个物品
    dp[0][i] = i / v[0] * w[0];
}
复制代码

根据动态转移方程,我们有下面的代码:

for (int i = 1; i < N; ++i) {
    for (int j = 0; j <= V; ++j) {
        if (j >= v[i]) {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]);
        }
        else {
            dp[i][j] = dp[i - 1][j];
        }
    }
}
复制代码

C++完整代码如下:

#include <iostream>
using namespace std;
#define MAX_LEN 10000

int w[MAX_LEN];
int v[MAX_LEN];
int dp[MAX_LEN][MAX_LEN];
int N, V;

int backpack() {
	// 对第0行进行初始化操作
	for (int i = 0; i <= V; ++i) {
		// 如果只能选择第一个数据,而且能选无数次
		// 那就将所有的容量都哪来装第一个物品
		dp[0][i] = i / v[0] * w[0];
	}
	for (int i = 1; i < N; ++i) {
		for (int j = 0; j <= V; ++j) {
			if (j >= v[i]) {
				dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]);
			}
			else {
				dp[i][j] = dp[i - 1][j];
			}
		}
	}
	return dp[N - 1][V];
}
复制代码

java代码如下:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int N = scanner.nextInt();
    int V = scanner.nextInt();
    int[] w = new int[N];
    int[] v = new int[N];
    int[][] dp = new int[N][V + 1];
    for (int i = 0; i < N; i++) {
        v[i] = scanner.nextInt();
        w[i] = scanner.nextInt();
    }

    // 对第0行进行初始化操作
    for (int i = 0; i <= V; ++i) {
        // 如果只能选择第一个数据,而且能选无数次
        // 那就将所有的容量都哪来装第一个物品
        dp[0][i] = i / v[0] * w[0];
    }

    for (int i = 1; i < N; ++i) {
        for (int j = 0; j <= V; ++j) {
            if (j >= v[i]) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - v[i]] + w[i]);
            }
            else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    System.out.println(dp[N - 1][V]);
}
复制代码

完全背包数组优化

在前面的内容当中我们已经仔细分析了完全背包问题的动态转移方程,我们发现在两层循环的内层循环当中,必须从0遍历到V,因为我们后面的数据依赖同一行前面的数据,还有就是依赖上一行相同列的数据,比如dp[i][j]依赖dp[i][0]dp[i][j - 1],还依赖dp[i - 1][j],这个其实是可以只用一个数组进行计算的。

我们先来看单行完全背包的Java代码:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int N = scanner.nextInt();
    int V = scanner.nextInt();
    int[] w = new int[N];
    int[] v = new int[N];
    int[] dp = new int[V + 1];

    for (int i = 0; i < N; i++) {
        v[i] = scanner.nextInt();
        w[i] = scanner.nextInt();
    }

    for (int i = 0; i < N; i++) {
        for (int j = v[i]; j <= V; j++) {
            dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    System.out.println(dp[V]);
}
复制代码

下面我们举个使用单行数组优化的例子:

假如我们正在更新dp[j],此时dp[j]还是第i-1行的状态而dp[0]dp[j-1]已经是第i行的状态了,因为我们是从前到后进行遍历。而要计算dp[j]的第i行状态的结果,那么他需要dp[0]dp[j-1]的第i行状态的数据,dp[j]的第i-1行状态的数据,而实际情况数组能够满足这种依赖关系,因此我们可以使用单行数组优化。

这个单行数组与多行数组的区别是多行数组每一行有每一行的状态,而且他保存了所有行的状态,单行数组只存在一行的状态,但是他会经历所有行的状态,也就是在外层for循环下单行数组的状态不断更新。(如果你不理解这段话和上面谈到的单行数组优化,可以结合代码和文字进行理解)

C++代码:

#include <iostream>
using namespace std;
#define MAX_LEN 10000

int w[MAX_LEN];
int v[MAX_LEN];
int dp[MAX_LEN]
int N, V;

int backpack() {
	for (int i = 0; i < N; ++i) {
		for (int j = v[i]; j <= V; ++j) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	return dp[V];
}

int main() {

	cin >> N >> V;
	for (int i = 0; i < N; ++i) {
		cin >> v[i] >> w[i];
	}
	cout << backpack();
	return 0;
}
复制代码

总结

在仔细分析完完全背包问题之后,我们可以总结一下动态规划的套路:

  • 分析问题设置dp数组并分析数组的含义。
  • 寻找动态转移方程,分析dp数组当中数据之间的依赖关系,我们在迭代的时候一定要遵循这个关系。
  • 初始化数组。
  • 进行数组dp求解。
  • 分析动态转移方程的含义和数据依赖关系,分析是否能够在不破坏数据依赖关系的条件下,优化数组空间。

其实上面的套路对于大多数动态规划的题目都有用的,因为动态规划的流程基本一致,而动态规划最难的地方就是第一步了,通过分析问题寻找动态转移方程。

 

动态规划背包问题九讲:从01背包到树形依赖的完整指南 动态规划是解决最优化问题的核心算法范式,其核心思想是通过状态定义和状态转移,将复杂问题分解为重叠子问题,避免重复计算。背包问题作为动态规划的经典模型,完美诠释了这一思想,通过‘容量’与‘价值’的约束关系,为资源分配、组合优化等场景提供了高效的解决方案。在技术面试和工程实践中,背包问题及其变体(如01背包完全背包、多重背包)是检验算法设计与问题抽象能力的重要标尺。本文以‘背包问题体系(九讲)’为核心,系统梳理了从基础的状态转移、空间优化(一维数组逆序/正序遍历),到高级的二进制优化、分组背包及树形依赖等关键 阅读详情

相关推荐

背包算法详解:从动态规划核心到实战应用

动态规划是解决最优化问题的经典算法范式,其核心思想是将复杂问题分解为重叠子问题,并通过存储子问题的解来避免重复计算,从而显著提升效率。在算法与数据结构领域,动态规划常用于资源分配、路径规划等场景,其技术价值在于为NP难问题提供了高效的近似或精确解法。背包问题作为动态规划的入门经典,完美诠释了这一思想,它模拟了在容量限制下如何选择物品以实现总价值最大化这一通用决策模型。无论是服务器资源调度、投资组合优化,还是广告预算分配,都可以抽象为背包模型。本文深入剖析了0-1背包完全背包及多重背包等核心变体,并结合二进

weixin_42116058的博客 276

九大类背包问题专题--合辑篇(详解)

1. 01背包问题 问题: 有N件物品和一个容量是V的背包。 第i件物品的体积是vi,价值是wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值最大。 输入格式 第一行有两个整数,N,V用空格隔开,分别表示物品数量和背包容积。 接下来有N行,每行两个整数vi,wi,用空格隔开,分别表示第i件物品的体积和价值。 输出格式 输出一个整数,表示最大价值 数据范围 0<N.V...

gl620321的博客 4152

C#动态规划实战:高效解决背包问题的工业级代码解析

动态规划就像恋爱——初学时只会暴力枚举,成熟后懂得记忆化搜索,最终在0-1背包的怀抱中找到真爱。

java专栏 1197

背包和仓库常见问题

问:背包有什么用?   答:背包可以放入各种道具.(任务道具,装备道具,宝石道具,其他   问:怎么增加背包数量?   答:首先要在背包栏右下角点击购买背包,需要消耗金子。然后在到杂货商人购买行囊,用左键点击拖到购买的扩展栏上就可以了。   问:背包中的道具不想要了怎么办?   答:可以到npc出售,有些道具无法出售可以使用背包栏右上角的摧毁道具。可以先点击这个道具,

boyhailong的专栏 1525

完全背包问题

完全背包问题 有N种物品和一个容量是V的背包,每种物品都有无限件可用。 第i种物品的体积是vi,价值是wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。 输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。 接下来有N行,每行两个整数vi,wi,用空格隔开,分别表示第i种物品的体积和价值。 ...

策马奔腾向前冲 415

面试高频手撕算法 - 背包问题1

面试高频手撕算法 - 01背包

xaiobit_hl的博客 1768

java算法题楼梯,程序员必学算法「动态规划」:爬楼梯(完全背包解法)

通知:我将公众号文章和学习相关的资料整理到了Github :https://github.com/youngyangyang04/leetcode-master,方便大家在电脑上学习,可以fork到自己仓库,顺便也给个star支持一波吧!之前讲这道题目的时候,因为还没有讲背包问题,所以就只是讲了一下爬楼梯最直接的动规方法(斐波那契)。这次终于讲到了背包问题,我选择带录友们再爬一次楼梯!此情此景,借...

weixin_36278982的博客 787

C++实现背包问题:从01背包到多重背包的算法精讲与代码实战

动态规划是解决组合优化问题的核心思想,它通过将复杂问题分解为重叠子问题并存储其解,避免了重复计算,从而高效求解。其核心在于状态定义与转移方程的设计,这一思想在算法设计与工程优化中具有极高的技术价值。背包问题作为动态规划的经典模型,广泛应用于资源分配、投资组合、项目调度等实际场景。本文聚焦于使用C++语言,深入解析背包问题的三大基础模型:01背包完全背包和多重背包。文章不仅阐述了动态规划的状态转移原理,更重点剖析了**一维DP优化**中至关重要的**循环顺序**差异(01背包逆序与完全背包正序),并探讨了多

weixin_30843121的博客 343

多重背包数量太大怎么办:面试官追问二进制拆分

样例容量十,物品 A 重三值五最多三件,拆成一件组和两件组;物品 B 重四值六最多两件,拆成一件组和一件余数组。最优选择是两件 A 加一件 B,重量十、价值十六。三件 A 价值十五仍略小;两件 B 加零件 A 会超重。测试直接断言十六,并用一件数量巨大的重五物品验证先按截断后仍只需少数组包。二进制拆分的价值,是用对数个零一选择精确覆盖零到 m 的所有件数。理解覆盖证明和倒序更新后,多重背包就不再是另一套陌生模板,而是对零一背包输入规模的一次压缩。

haerapi的博客 178

动态规划背包问题详解:从01背包到多重背包的C++实现与优化

动态规划是解决组合优化问题的核心算法思想,其核心在于将复杂问题分解为重叠子问题,并通过状态转移方程高效求解。在算法与数据结构领域,背包问题是动态规划的经典范例,它抽象了在资源有限条件下进行最优决策的通用模型,具有极高的技术价值。该模型广泛应用于资源分配、投资组合、调度优化等实际工程场景。本文聚焦于背包问题的三大核心变种:01背包完全背包和多重背包,深入剖析其状态定义、转移方程及C++实现,并特别探讨了滚动数组空间优化和二进制拆分等关键技巧。针对算法实践中常见的初始化陷阱、遍历顺序混淆等难点,提供了清晰的排

weixin_30572613的博客 384

「代码随想录」70. 爬楼梯【动态规划】(完全背包解法)

以前我没得选,现在我选择再爬一次

代码随想录 826

八十八、从斐波那契数列和零一背包问题探究动态规划

本人看了vivo,阿里巴巴的校招算法题,可以明确知道绝对有动态规划。如果没有,那么出题的面试官真的没有水平。跌了N次的动态规划,Runsen最近也拼命搞动态规划。这篇文章浪费了三天时间。

微信号:RunsenLiu 1519

代码随想录第四十六天|多重背包背包问题总结篇

代码随想录第四十六天|多重背包背包问题总结篇

muzi_taibai的博客 537

C++面试必考:动态规划背包问题核心原理与工程实践详解

动态规划是解决最优化问题的核心算法思想,其本质是通过定义状态和状态转移方程,利用重叠子问题和最优子结构来避免重复计算。在算法面试中,背包问题是考察动态规划理解的经典模型,它清晰地体现了“选择”与“限制”的建模过程。掌握背包问题不仅能提升解决特定算法题的能力,更能深刻理解资源分配、成本效益分析等工程场景下的通用求解框架。本文聚焦于C++实现,深入剖析01背包完全背包等变种的核心状态转移与空间优化技巧,并结合【秋招面试】高频题型与【C++调试】实践,帮助读者构建从问题识别、模型抽象到代码实现的完整能力体系。

weixin_34198453的博客 448

算法训练第四十五天|70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数

算法训练第四十五天|70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数

weixin_43434369的博客 1346

0-1背包,分割等和子集(不用0-1背包框架),零钱兑换Ⅱ

背包问题使用动态规划来解决

cyz322649的博客 727

从嵌入式春招到秋招:我用C语言刷动态规划(背包问题)的实战心得

本文分享了嵌入式工程师如何通过C语言刷动态规划(背包问题)提升算法能力的实战心得。从暴力递归到记忆化搜索,再到背包问题的三大变种(01背包完全背包、多重背包)及优化技巧,作者详细解析了动态规划在嵌入式开发中的应用价值,并提供了笔试实战中的避坑指南和系统刷题路线规划。

weixin_42524824的博客 314

动态规划经典面试题:背包问题的状态转移方程推导与实战

◦ 放入背包:前提是背包容量 j 大于等于第 i 个物品的重量 weights[i],此时 dp[i][j] = dp[i - 1][j - weights[i]] + values[i],即放入第 i 个物品后,背包容量减少为 j - weights[i],再加上第 i 个物品的价值 values[i],等于考虑前 i - 1 个物品,背包容量为 j - weights[i] 时的最大价值加上第 i 个物品的价值。1. 初始化:与0-1背包问题相同,dp[0][j] = 0,dp[i][0] = 0。

2501_91651594的博客 432
上一篇: 面试被问MySQL 如何去重,还有谁不会?
下一篇: 新一代HTAP数据库崛起,MySQL生态的最佳归宿?
java界泥石流
博客等级 码龄5年 53粉丝 40原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值