[dp21_完全背包] 零钱兑换 II | 完全平方数

目录

1.零钱兑换 II

题解

2.完全平方数

题解


背包问题无非就两种,求最大最小或者总共数量
区别就是在DP表达式的更新上面

  • 一个是取Max或min
  • 还有一个就是在原基础上满足条件的话实现加一的操作.

初始化的话 不满足条件的地方更新为最大或者最小,就是要更新一个取不到的值(推荐INF)
想实现加一操作的时候就初始化为0

1.零钱兑换 II

链接:518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

  • 当看到硬币有无限个,我们就该想到这是一道完全背包问题。

题解

1.状态表示

  • dp[i][j] 表示:从前 i 个硬币中挑选,总和正好等于j,一共有多少种选法

2.状态转移方程

根据最后一个位置,划分情况

  • 不选 i,说明所有选法中都不包含第 i 个硬币,相当于从去 1 ~ i - 1 这个区间去选,就是dp[i-1][j]
  • 选1个i,这时就有 i 这个硬币了,然后仅需去 1 ~ i - 1 区间去选一个 总和正好等于j - coins[i],注意问的是多少种选法, i 这个硬币仅需跟在所有选法后面就行了,不需要加1。
  • 选2个i,选3个i … 都是类似分析

发现填一个状态的时候发现这个状态时候很多状态拼接而成的,这个时候我们要想到策略把这些状态用一个或者两个状态来表示。

  • 在完全背包哪里我们已经分析过了,这里直接写。
    注意这里直接写,是因为前面已经分析过了,这个状态只适用于完全背包。
  • 最后我们将所有情况加起来,注意 j-coins[i] 不一定存在。
  • 必须要满足 j >= coins[i],才能用这个状态。

3.初始化

  1. 多开一行一列
  2. 里面的值要保证后序的填表是正确的
  3. 下标的映射关系

第一列不用初始化,因为用到dp[i][j-coins[i]] 前提 j >= coins[i],所以不会越界。我们只初始化第一行。

  • 第一行表示硬币为空,当 j = 0表示总和为0,不选就行了,这是一种选法
  • j = 1、2、3…,硬币为空根本凑不出总和等于j的选法。直接都给0就行了

4.填表顺序

  • 填dp[i][j]会用到上面和左边的值
  • 因此从上往下填写每一行,每一行从左往右。

5.返回值

  • dp[i][j] 表示:从前 i 个硬币中挑选,总和正好等于j,一共有多少种选法
  • 我们要的是从整个数组种选总和等于amount一共有多少种选法,正好就是dp[n][amount]
class Solution {
typedef unsigned long long ll;
public:
int change(int amount, vector<int>& coins) 
{
    int n=coins.size();
    //选法 -- 求和
    vector<vector<ll>> dp(n+1,vector<ll>(amount+1,0));
    dp[0][0]=1;

    for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=amount;j++)
                {
                    //下标 映射
                    dp[i][j]=dp[i-1][j];
                    if(j>=coins[i-1])
                        dp[i][j]+=dp[i][j-coins[i-1]];
                }
        }
    return (int)dp[n][amount];
}
};
  • 这里存在溢出要用unsigned long long
  • 或者用一维数组来进行优化,因为一个的状态表示,只用到了上一行的内容

优化

class Solution {
typedef unsigned long long ll;
public:
int change(int amount, vector<int>& coins) 
{
    int n=coins.size();
    //选法 -- 求和
    vector<ll> dp(amount+1,0);
    dp[0]=1;

    for(int i=1;i<=n;i++)
        {
            for(int j=coins[i-1];j<=amount;j++)
                {
                    //下标 映射
                    dp[j]+=dp[j-coins[i-1]];
                    //实现 剪枝优化
                }
        }
    return (int)dp[amount];
}
};

2.完全平方数

链接: 279. 完全平方数

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

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

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

示例 2:

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

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

完全平方数:比如说1^2 = 1,2^2 = 4,3^2 = 9。1、4、9就是完全平方数。

题解

  • 从左往右开始挑完全平方数,一个数可以挑多次,挑出来的数只要等于n即可。
  • 如果对背包问题比较敏感,这就是背包问题,并且是完全背包问题。

1.状态表示

  • dp[i][j] 表示:从前 i 个完全平方数种挑选,总和正好等于 j,所有选法中,最少的数量

2.状态转移方程

  • 根据最后一个位置,划分情况
  • 不选 i^2,说明所有选法中都不包含 i^2 这个平方数,相当于从去 1 ~ i - 1 这个区间去选,就是dp[i-1][j]
  • 选1个 i^2,然后在去1 ~ i - 1区间挑一个总和等于 j - i^2的最少数量,然后在加上选的 i^2这一个数。
  • 同理选2个 i^2,3个 i^2…, 和上面分析一样

发现填一个状态的时候发现这个状态时候很多状态拼接而成的,这个时候我们要想到策略把这些状态用一个或者两个状态来表示。

  • 在完全背包哪里我们已经分析过了,这里直接写。
    然后从所有情况找最小值

3.初始化

  1. 多开一行一列
  2. 里面的值要保证后序的填表是正确的
  3. 下标的映射关系

第一列不用初始化,因为用到dp[i][j-coins[i]] 前提 j >= coins[i],所以不会越界。

  • 我们只初始化第一行。
  • 第一行表示完全平方数为0,当 j = 0表示总和为0,不选就行了,最少数量为0
    当 j = 1、2…,完全平方数为0,根本凑不出和为 j,然后我们填dp[i][j]要最小值,为了不让这些位置得值影响填表,因此可以给0x3f3f3f3f

4.填表顺序

  • 填dp[i][j]会用到上面和左边的值
  • 因此从上往下填写每一行,每一行从左往右。

5.返回值

dp[i][j] 表示:从前 i 个完全平方数中挑选,总和正好等于 j,所有选法中,最少的数量,但是我们要的是从整个完全平方数种选总和正好等于n最少数量。

这里有一个问题 i 取到哪里比较好呢?

  • 我们绝对不会选一个完全平方数会比n大,所以说下标的平方应该小于等于n
  • 所以我们最终返回的是 dp[√n][n],同时建表的时候也应该是(√n + 1)*(n + 1)规模的。
class Solution {

const int INF=0x3f3f3f3f;
public:
int numSquares(int n) 
{
    int m=sqrt(n);
    vector<vector<int>> dp(m+1,vector<int>(n+1,INF));
    dp[0][0]=0;

    for(int i=1;i<=m;i++)
        {
            for(int j=0;j<=n;j++)
                {
                    dp[i][j]=dp[i-1][j];
                    if(j>=i*i)
                        dp[i][j]=min(dp[i][j],dp[i][j-i*i]+1);
                }
        }
    return dp[m][n];
}
};

优化

  • 只和上一行有关
class Solution {

const int INF=0x3f3f3f3f;
public:
int numSquares(int n) 
{
    int m=sqrt(n);
    vector<int> dp(n+1,INF);
    dp[0]=0;

    for(int i=1;i<=m;i++)
        {
            for(int j=i*i;j<=n;j++)  //背包容量 至少要有物品这么大
                {
                    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、付费专栏及课程。

余额充值