【算法挨揍日记】day44——518. 零钱兑换 II、279. 完全平方数

 518. 零钱兑换 II

518. 零钱兑换 II

题目描述:

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

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

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

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

【算法挨揍日记】day44——518. 零钱兑换 II、279. 完全平方数_第1张图片

解题思路:

算法思路:
先将问题「转化」成我们熟悉的题型。
i. 在⼀些物品中「挑选」⼀些出来,然后在满⾜某个「限定条件」下,解决⼀些问题,⼤概率
是背包模型;
ii. 由于每⼀个物品都是⽆限多个的,因此是⼀个「完全背包」问题。
接下来的分析就是基于「完全背包」的⽅式来的。
1. 状态表⽰:
dp[i][j] 表⽰:从前 i 个硬币中挑选,总和正好等于 j ,⼀共有多少种选法。
2. 状态转移⽅程:
线性 dp 状态转移⽅程分析⽅式,⼀般都是「根据最后⼀步」的状况,来分情况讨论。但是最后
⼀个物品能选很多个,因此我们的需要分很多情况:
i. 0 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j
此时最少的硬币个数为 dp[i - 1][j]
ii. 1 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j -
v[i] 。因为挑选了⼀个 i 硬币,此时最少的硬币个数为 dp[i - 1][j -
coins[i]] + 1
ii. 2 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j
- 2 * coins 。因为挑选了两个 i 硬币,此时最少的硬币个数为 dp[i - 1][j -
2 * coins[i]] + 2
iv. ......
结合我们在完全背包⾥⾯的「优化思路」,我们最终得到的状态转移⽅程为:
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]] + 1
这⾥教给⼤家⼀个技巧,就是相当于把第⼆种情况 dp[i - 1][j - coins[i]] + 1
⾯的 i - 1 变成 i 即可。
3. 初始化:
初始化第⼀⾏即可。
第⼀⾏表⽰没有物品,没有物品正好能凑能和为 0 的情况。因此 dp[0][0] = 1 ,其余位置都
0 种情况。
4. 填表顺序:
根据「状态转移⽅程」,我们仅需「从上往下」填表即可。
5. 返回值:
根据「状态表⽰」,返回 dp[n][V]

解题思路:

class Solution {
public:
    int change(int amount, vector& coins) {
        int n=coins.size();
        vector>dp(n+1,vector(amount+1));
        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 dp[n][amount];
    }
};

 空间优化后的代码:

class Solution {
public:
    int change(int amount, vector& coins) {
        int n=coins.size();
        vectordp(amount+1);
        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 dp[amount];
    }
};

 279. 完全平方数

279. 完全平方数

题目描述:

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

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

解题思路:

算法思路:
这⾥给出⼀个⽤「拆分出相同⼦问题」的⽅式,定义⼀个状态表⽰。(⽤「完全背包」⽅式的解法
就仿照之前的分析模式就好啦~~)
为了叙述⽅便,把和为 n 的完全平⽅数的最少数量简称为「最⼩数量」。
对于 12 这个数,我们分析⼀下如何求它的最⼩数量。
如果 12 本⾝就是完全平⽅数,我们不⽤算了,直接返回 1
但是 12 不是完全平⽅数,我们试着把问题分解⼀下:
1. 情况⼀:拆出来⼀个 1 ,然后看看 11 的最⼩数量,记为 x1
2. 情况⼆:拆出来⼀个 4 ,然后看看 8 的最⼩数量,记为 x2 ;(为什么拆出来 4
⽽不拆出来 2 呢?)
3. 情况三:拆出来⼀个 8 ......
其中,我们接下来求 11 8 的时候,其实⼜回到了原来的问题上。
因此,我们可以尝试⽤ dp 的策略,将 1 2 3 4 6 等等这些数的最⼩数量依次保存起来。再求
较⼤的 n 的时候,直接查表,然后找出最⼩数量。
1. 状态表⽰:
dp[i] 表⽰:和为 i 的完全平⽅数的最少数量。
2. 状态转移⽅程:
对于 dp[i] ,根据思路那⾥的分析我们知道,可以根据⼩于等于 i 的所有完全平⽅数 x 进⾏
划分:
x = 1 时,最⼩数量为: 1 + dp[i - 1]
x = 4 时,最⼩数量为: 1 + dp[i - 4] ......
⼀直枚举到 x <= i 为⽌。
为了⽅便枚举完全平⽅数,我们采⽤下⾯的策略: for(int j = 1; j * j <= i; j++)
综上所述,状态转移⽅程为:
dp[i] = min(dp[i], dp[i - j * j] + 1)
3. 初始化:
n = 0 的时候,没法拆分,结果为 0
n = 1 的时候,显然为 1
4. 填表顺序:
从左往右。
5. 返回值:
根据题意,返回 dp[n] 的值。

解题代码:

class Solution {
public:
    int numSquares(int n) {
        vector dp(n + 1);
        dp[1] = 1;                   // 初始化
        for (int i = 2; i <= n; i++) // 枚举每个数
        {
            dp[i] = 1 + dp[i - 1];           // ⾄少等于 1 + dp[i - 1]
            for (int j = 2; j * j <= i; j++) // ⽤⼩于 i 的完全平⽅数划分区间
                dp[i] =
                    min(dp[i], dp[i - j * j] + 1); // 拿到所有划分区间内的最⼩值
        }
        // 返回结果
        return dp[n];
    }
};

你可能感兴趣的:(算法挨揍日记,Leetcode,算法,数据结构)