动态规划系列:6.背包问题

本文介绍了动态规划在解决背包问题中的应用,包括0-1背包、完全背包、目标和、分割等和子集等问题,给出了相应的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包问题

0-1背包问题

1. 0-1背包问题

题目描述:

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

题目分析:

设f(n,w)前n件物品放到一个容量为w的背包中可以获得最大价值,

考虑我们的子问题,将前i件物品放到容量为v的背包中,若我们只考虑第i件物品时,它有两种选择,放或者不放。

  1. 如果第n件物品不放入背包中,那么问题就转换为:将前in- 1件物品放到容量为v的背包中,带来的收益f(n-1)

  1. 如果第n件物品能放入背包中,那么问题就转换为:将前n - 1件物品放到容量为w - weight[i]的背包中,带来的收益f(n-1) + cost[n]则有

代码

int bag_problem(const vector<int>&weight,const vector<int>&value,const int bagweight) {
	int obj_size = weight.size();//物品数量
	vector<vector<int>> dp(obj_size, vector<int>(bagweight+1, 0));//背包容量从0到bagweight
	for (int k = weight[0]; k <= bagweight; k++) {
		dp[0][k] = value[0];
	}
	for (int i = 1; i < obj_size; i++) {
		for (int j = 1; j <= bagweight; j++) {
			if (weight[i] > j) dp[i][j] = dp[i - 1][j];
			else {
				dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
			}
		}
	}
	return dp[obj_size-1][bagweight];
}

代码简化

上面的代码中,dp(i)的求解只与dp(i-1)有关,与dp(i-2),dp(i-3)....都无关,因此前面的dp(i-2),...都可以覆盖掉

因此可以只用一个一维的dp数组表示,

dp[j]表示从前i件物品选取任意物品装入容量为j的背包所得的最大价值;注意:为了便于理解前面还是加上了i,实际上dp[j]相当于前面二维dp的dp(i:0---n,j)

递推公式

i从0到i
因为求dp[j]依赖于dp[0到j]的值,因此,在i的一次循环内,应该先求j比较大的dp[j],再依次求j比较小的dp[j],这样才能保证求dp[j]的时候使用的是上一轮循环求得dp[0-j]的值,而不是在此轮循环被更新的dp[0-j]

int bag_problem(const vector<int>&weight, const vector<int>&value, const int bagweight) {
	vector<int> dp(bagweight + 1, 0);//dp[j]表示在容量为j时能获得的最大价值
	for (int i = 0; i < weight.size(); i++) {//依次求在0-i件物品随机选取放入容量为0-bagweight的背包的最大价值
		for (int j = bagweight; j >= weight[i]; j--)
			dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
	}
	return dp[bagweight];
}

2.分割等和子集

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

示例 1:

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

示例 2:

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

分析

将一个数组划分为两个元素和相等的子集,首先,整个数组和得是偶数

设整个数组和为X,在整个数组里找某些元素使这些元素的和为X/2,找到了则返回true,反之返回false

可以类似与背包问题:

  • 背包的体积为X / 2

  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值

  • 背包如果正好装满(达到最大价值),说明找到了总和为 sum / 2 的子集。

class Solution {
public:
	bool canPartition(vector<int>& nums) {
		int sum = 0;
		for (int ele : nums) sum += ele;
		if (sum % 2 != 0) return false;
		int bag_weight = sum / 2;
		vector<vector<int>> dp(nums.size(), vector<int>(bag_weight + 1, 0));
		for (int k = nums[0]; k <= bag_weight; k++) dp[0][k] = nums[0];
		for (int i = 1; i < nums.size(); i++) {
			for (int j = 1; j <= bag_weight; j++) {
				if (j < nums[i]) dp[i][j] = dp[i - 1][j];
				else {
					dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
				}
			}
		}
		return dp[nums.size() - 1][bag_weight] == bag_weight;
	}
};

一维dp解决

物品重量:nums[1], nums[2],...,nums[n]

物品价值:nums[1], nums[2],...,nums[n]

背包容量:sum{num}/2

class Solution {
public:
	bool canPartition(vector<int>& nums) {
		int sum = 0;
		for (int ele : nums) sum += ele;
		if (sum % 2 != 0) return false;
		int bag_weight = sum / 2;
		vector<int> dp(bag_weight + 1, 0);
		for (int i = 0; i < nums.size(); i++) {
			for (int j = bag_weight; j >= nums[i]; j--) {
				dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
			}
		}
		return dp[bag_weight] == bag_weight;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值