代码随想录算法训练营第42天|0-1背包基础理论,416.分割等和子集

0-1背包基础理论

01 背包

问题描述

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

暴力解法

  • 每一件物品其实只有两个状态,取或者不取,因此可以用回溯法,求出所有的方案取最大值。时间复杂度O(2^n)

动态规划

二维dp数组01背包

dp[i][j] 的含义:

  • dp[i][j] 表示从下标为[0, i]的物品中任取,放进容量为 j 的背包,价值总和的最大值。

递推公式:

  • 不放物品 i :dp[i - 1][j],即从[0, i - 1]的物品中任取,放进容量为 j 的背包的最大价值
  • 放物品 i :dp[i - 1][j - weight[i]] + value[i],即从[0, i - 1]的物品中任取放进背包容量为 j - weight[i]的最大价值 + 物品 i 的价值
  • 递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

dp数组初始化:

  • dp[i][j] 由左上方向的值和上方的值共同推导得来
  • 因此,需要初始化第一行第一列
  • 第一行为把物品 0 分别放进重量为 0 到 j 的背包的最大价值
  • 第一列为把物品[0, i] 放进重量为 0 的背包的最大价值

遍历顺序:

  • 先遍历物品后遍历背包,或者先遍历背包后遍历物品都可以(因为从左上和上推导)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void test2_wei_bag_problem1() {
	vector<int> weight = { 1, 3, 4};
	vector<int> value = { 15,20,30 };
	int bagweight = 4;
	vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
	// 初始化
	for (int j = weight[0]; j <= bagweight; j++) {
		dp[0][j] = value[0];
	}
	for (int i = 1; i < weight.size(); i++) {
		for (int j = 0; j <= bagweight; j++) {
			if (j < weight[i]) dp[i][j] = dp[i - 1][j];
			else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
		}
	}
	cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
	test2_wei_bag_problem1();
	return 0;
}
一维dp数组(滚动数组)

滚动数组:上一层可以重复利用,直接拷贝到当前层

  • dp[j] 表示:容量为j的背包,所背的物品价值可以最大为dp[j]
  • 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
  • 初始化:dp 数组初始化为 0
  • 遍历顺序:外层物品,内层重量,内层倒序遍历(正序遍历会覆盖滚动数组的上一层的值)
  • 上一层是不放物品i的值,下一层是有可能放了物品i的值,所以正序遍历会覆盖原有的值,导致物品i被加入多次
  • 不可以颠倒内外层:背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品
void test1_wei_bag_problem1() {
	vector<int> weight = { 1, 3, 4 };
	vector<int> value = { 15,20,30 };
	int bagweight = 4;
	vector<int> dp(bagweight + 1);
	for (int i = 0; i < weight.size(); i++) {
		for (int j = bagweight; j >= weight[i]; j--) {
			dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
		}
	}
	cout << dp[bagweight] << endl;
}
int main() {
	test1_wei_bag_problem1();
	return 0;
}

416.分割等和子集

力扣题目链接

思路

动态规划
  • 核心:从数组中任取数字使其和成为原数组和的一半
  • 可转化为01背包问题,weight[i]==value[i]==nums[i]
  • dp[j] 装满容量为j的背包的最大价值
  • 当dp[j] == target时,说明背包被装满
  • 递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
  • 初始化为0
  • 遍历顺序:内层从大到小,外层物品,内层背包
回溯
  • 收集所有子集
  • 如果有一个子集的和(子集大小<nums.size())==数组和的一半,则返回true
  • 力扣超时

代码

动态规划
class Solution {
public:
    /* 核心:从数组中任取数字使其和成为原数组和的一半
     * 可转化为01背包问题,weight[i]==value[i]==nums[i]
     * dp[j] 装满容量为j的背包的最大价值
     * 当dp[j] == target时,说明背包被装满
     * 递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
     * 初始化为0
     * 遍历顺序:内层从大到小,外层物品,内层背包
    */
    bool canPartition(vector<int>& nums) {
        int sum =  0;
        for (int i = 0; i < nums.size(); i++)
            sum +=nums[i];
        if (sum % 2 == 1) return false;
        int target = sum / 2;
        vector<int> dp(target + 1, 0);
        for (int i = 0; i < nums.size(); i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        if (dp[target] == target) return true;
        return false;
    }
};
  • 时间复杂度O(n*target)
  • 空间复杂度O(target)
回溯
class Solution {
public:
    bool flag = false;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex, int sum) {
        if (!path.empty() && path.size() < nums.size()) {
            int ans = 0;
            for (int i = 0; i < path.size(); i++)
                ans += path[i];
            if (ans == sum) flag = true;
        }
        for (int i = startIndex; i < nums.size() && !flag; i++) {
            path.push_back(nums[i]);
            backtracking(nums, i+1, sum);
            path.pop_back();
        }
    }
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++)
            sum +=nums[i];
        if (sum % 2 == 1) return false;
        backtracking(nums, 0 , sum/2);
        return flag;
    }
};
  • 时间复杂度O(2^n)
  • 空间复杂度O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值