0 1背包 填表实现

01背包问题是一个经典的问题了,接下来使用动态规划思想,填表法来解决一下这个问题吧。

填表法就基本的操作步骤 :

第一步:分析问题的状态变化,将在所有物品里得到的最大价值分割成子问题,子问题:只有一个物品的时候,得到的最大价值,有两个物品的时候得到的最大价值,随着物品数不断增加,每增加一个物品得到的最大的价值。

只有一个物品的时候,很容易得到最大的价值,升级为两个物品呢,这时候就有取舍了,就是在袋子当前的容量下要不要将物品放入袋子中?首先取决于,物品能不能放进袋子,如果能放进袋子里,价值怎么来衡量?带着这些问题,去表格中寻找规律吧。

第二步:分析问题,确定初值,针对这个问题很容易想到背包空间为0的时候,里面的价值为0。

第三步:分析分析问题,确定表格的形式,横轴纵轴各代表什么含义。

首先声明一点,填表的过程你也不知道规律是什么,完全按照题目的规则去填表,当然如何设计表格也是要一定的题量之后可能会涌现灵感,或者在分析问题阶段,按在子问题的方式设计表格。比如这道题,每个子问题都是增加一个物品之后的到的最大价值,这样确定了表格的一个轴,即物品数,另一个轴代表不同背包容量下的最大价值。

nameweightvalue12345678910
a26066991212151515
b23033669991011
c65000666661011
d54000666661010
e460006666666

 

上图是复制的,懒得画了,相信已经解释的很清楚了,这个图是含义是。有物品a,b,c,d,e, 重量和价值如图,从下往上填表,每一行代表:包括这个行和它下面的这写物品可选。

找一下上面抛出问题的答案吧,从倒数第二行看,背包容量不够装当前物品的时候,那就看看以前装填价值的情况即下面的值,当可以装上当前物品的时候,那么它肯定会占用其他物品的空间呢,我们尝试装进去,去看看将背包剩余空间还能装多少价值,这就是为什么要将背包容量也分割成子问题的原因了,不然到时候去哪查呀,剩余空间能装的价值当然还要从当前行的下一行找,因为下一行不包含当前的物品。因为是尝试装,装完得到值还要和不装的时候比大小吧。到此传说中的状态转移方程是不是清晰了:

dp[i][j]  = max(dp[i-1][j] , dp[i-1][j- w[i]]+v[i])   w表示重量 v 表示价值。

当然如果不够装,那就不装了呗,所以dp[i-1][j- w[i]]  >=0  和 <0 的情况,

链接:https://www.nowcoder.com/questionTerminal/708f0442863a46279cce582c4f508658
来源:牛客网

现有一个容量大小为V的背包和N件物品,每件物品有两个属性,体积和价值,请问这个背包最多能装价值为多少的物品?

输入描述:

第一行两个整数V和n。
接下来n行,每行两个整数体积和价值。1≤N≤1000,1≤V≤20000。
每件物品的体积和价值范围在[1,500]。

输出描述:

输出背包最多能装的物品价值。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
	int v = 0;
	int n = 0;
	cin >> v >> n;
	vector<vector<int>> m_v;
	//vector<int> t_tmp;
	//m_v.push_back(t_tmp);
	for(int i=0;i<n;++i)
	{
		int a = 0;
		int b = 0;
		cin >> a >> b;
		vector<int> tmp;
		tmp.push_back(a);
		tmp.push_back(b);
		m_v.push_back(tmp);
	}
	vector<vector<int>> ret(n, vector<int>(v+1, 0));
	for (int i = 0; i < v + 1; ++i)
	{
		if (i >= m_v[0][0])
		{
			ret[0][i] = m_v[0][1];
		}
	}
	for (int i = 1; i < n; ++i)
	{
		for (int j = 0; j <= v; ++j)
		{
			int mem = 0;
			if (j - m_v[i][0] >= 0)
			{
				mem = ret[i - 1][j - m_v[i][0]];
				ret[i][j] = max(ret[i - 1][j], mem + m_v[i][1]);
			}
			else
			{
				ret[i][j] = ret[i - 1][j];
			}
		}
	}
	cout << ret[n-1][v];
	system("pause"); 
	return 0;
}

我的表是从上到下的,不要迷了。

背包扩展:

https://leetcode-cn.com/problems/partition-equal-subset-sum/

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

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

 其实这个问题也是一个背包的问题,只是没让求最大的价值,而是恰当的选择物品能不能刚好将背包的一一半装满,根据之前填表法的思路,我们将背包空间也做了递增的去扩大,现在只需要将背包空间设置为,所有数字和的一半,(这里有个问题,如果所有的和为奇数,那么一定不符合要求),其实就是只有容量一个维度的限制,我们来看表吧,表里面的值表示,当前背包容量下,能装的物品大小。

最终得到的值看看是不是背包容量的一半就行了。

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

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值