第35天 动态规划4 完全书包问题

完全书包

  • 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

  • 完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。

其与0-1背包的区别:背包循环不用倒序,切两个

377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

nums = [1, 2, 3]
target = 4
所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        //排列类型 因为不同顺序算不一样的
        //dp数组含义:dp[i] 目标为target的元素组合个数
        // 由于d[i]是由d[i-1]推出来的,至于为什么?排列组合就这样
        //所以递推公式:d[j]+=d[j-nums[i]]
        //初始化 如果都初始化0,那么所有的都是0,所以第一个初始化为1;
        vector<int>dp(target+1,0);
        dp[0]=1;
        //确定循环顺序,由于是排列:元素一样顺序不一样也算是一种结果,根据外层遍历来看,要先遍历背包,再遍历物品;
        for(int i=0;i<=target;i++){
            for(int j=0;j<nums.size();j++){
                if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]]; //是dp[i]不是dp[j],原因是定义dp[i]为背包容量为i时排列的最大个数,方括号里边应该是target;
            }
        }
        }
        return dp[target];
    }
};

70. 爬楼梯(进阶版)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

输入描述:输入共一行,包含两个正整数,分别表示n, m

输出描述:输出一个整数,表示爬到楼顶的方法数。

输入示例:3 2

输出示例:3

提示:

当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。

此时你有三种方法可以爬到楼顶。

1 阶 + 1 阶 + 1 阶段
1 阶 + 2 阶
2 阶 + 1 阶

动规五部曲分析如下:

确定dp数组以及下标的含义
dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。

确定递推公式
在动态规划:494.目标和 (opens new window)、 动态规划:518.零钱兑换II (opens new window)、动态规划:377. 组合总和 Ⅳ (opens new window)中我们都讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]

那么递推公式为:dp[i] += dp[i - j]

dp数组如何初始化
既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。

下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果

确定遍历顺序
这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!

每一步可以走多次,这是完全背包,内循环需要从前向后遍历。

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。

** 所以需将target放在外循环,将nums放在内循环。**

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for (int i = 0; i <= target; i++) { // 遍历背包
            for (int j = 0; j < nums.size(); j++) { // 遍历物品
                if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0
示例 4:

输入:coins = [1], amount = 1
输出:1
示例 5:

输入:coins = [1], amount = 2
输出:2
提示:

1 <= coins.length <= 12
1 <= coins[i] <= 2^31 - 1
0 <= amount <= 10^4

题目关键一点是判断 dp[j - coins[i]] != INT_MAX 不能超范围,或者说有没有更新过,而且这次的dp公式是求min,一般求min就要初始化为INT_MAX,防止为0,每次结果都是0.

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //完全背包,因为有无限个
        //dp[i] 凑成总金额i所需的 最少的硬币个数
        //递推公式dp=min(dp[i],dp[i-coins[i]]+1)
        //初始化
        vector<int>dp(amount+1,INT_MAX);
        dp[0]=0;
        //遍历方式
        for(int i=0;i<coins.size();i++){ //物品
            for(int j=coins[i];j<=amount;j++){ //背包 // 从coins[i]开始遍历,保证背包容量足够放下当前硬币 不从coins[i]遍历,后边j - coins[i]]会越界。
                if(dp[j - coins[i]] != INT_MAX )  dp[j]=min(dp[j],dp[j-coins[i]]+1); //为了确保 dp[j - coins[i]] 已经被计算过
            }
        }

        //还可以背包在外侧
        for(int i=0;i<=amount,i++){ //背包
            for(int j=0;j<coins.size();j++){ //物品
                if(coins[j]<=i && dp[j - coins[i]] != INT_MAX) dp[i]=min(dp[i],dp[i-coins[j]]+1);
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

279.完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9
提示:

1 <= n <= 10^4

完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?

注意遍历背包和遍历物品,遍历背包时是for(int j=i*i;j<=n;j++)

class Solution {
public:
    int numSquares(int n) {
        //完全背包,因为可以重复放物品
        //dp[i] 和为 i 的完全平方数的最少数量
        //递推公式  dp[i]=min(dp[i],dp[i-j*j]+1)
        //初始化
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;
        //遍历方式 谁在外边都可以
        for(int i=1;i<=n/2+1;i++) //物品
        {
            for(int j=i*i;j<=n;j++){ //背包 
            if(dp[j-i*i]!=INT_MAX) dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
        return dp[n];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值