文章目录
day45学习内容
day45主要内容
- 零钱兑换
- 完全平方数
声明
本文思路和文字,引用自《代码随想录》
一、零钱兑换
1.1、动态规划五部曲
1.1.1、 确定dp数组(dp table)以及下标的含义
- p[i]表示凑成金额i所需的最少硬币数。
1.1.2、确定递推公式
最少嘛,你就要想到min。和零钱兑换II有一点区别就是那一题求取所有的排列数,那一题已经把所有的求出来了,所以这一题,很自然的想到,要取一下min(dp[j],dp[j-nums[i]+1])
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
1.1.3、 dp数组如何初始化
dp[0] = 0;
1.1.4、确定遍历顺序
先遍历物品后遍历背包。并且是正序的,因为正序是表示可重复的。
1.2、代码
class Solution {
public int coinChange(int[] coins, int amount) {
int max = Integer.MAX_VALUE;
int[] dp = new int[amount + 1];
// 每个元素初始化为max,表示初始时没有任何一种硬币组合能够凑成对应的金额。
for (int j = 0; j < dp.length; j++) {
dp[j] = max;
}
// 初始化dp[0],为啥等于0.因为当金额为0时需要的硬币数目为0
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
// 先遍历物品再遍历背包
for (int j = coins[i]; j <= amount; j++) {
if (dp[j - coins[i]] != max) {
// 选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
// 如果dp[amount]等于max,说明没有任何一种硬币组合能够凑成总金额,因此返回-1;
// 否则,返回dp[amount],即凑成总金额所需的最少硬币数。
return dp[amount] == max ? -1 : dp[amount];
}
}
1.2.1、如何理解状态转移方程?
外层循环遍历所有给定的硬币面额,内层循环从当前硬币面额开始,直到总金额amount。对于内层循环中的每一个金额j,代码首先检查j减去当前硬币面额的结果(即dp[j - coins[i]])是否已经有了一个解(不是max),如果是,表示j金额可以通过加上一个当前面额的硬币得到。然后,通过Math.min(dp[j], dp[j - coins[i]] + 1)更新dp[j],即比较当前的最少硬币数和通过当前面额的硬币得到的硬币数+1哪个更小,选择较小的一个作为新的最少硬币数
1.2.2、为什么dp[j - coins[i]] + 1要+1
假设你已经知道了对于金额j-coins[i]
(即当前考虑的总金额减去一枚当前面额的硬币),最少需要dp[j - coins[i]]
枚硬币。那么,如果你向这个组合中再加上一枚coins[i]
面额的硬币,就可以得到金额j
。因此,凑成金额j
所需的最少硬币数就可以通过凑成金额j-coins[i]
所需的硬币数加上这一枚新加入的硬币来计算,即dp[j - coins[i]] + 1
。
这里的+1
正是代表加上这一枚新硬币。
举一个简单的例子,假设你有面额为1的硬币,你要凑成金额3。按照动态规划的过程,你会这样计算:
- 凑成金额0需要0枚硬币,即
dp[0] = 0
。 - 凑成金额1需要
dp[0] + 1 = 1
枚硬币。 - 凑成金额2需要
dp[1] + 1 = 2
枚硬币。 - 凑成金额3需要
dp[2] + 1 = 3
枚硬币。
每次+1
就是代表往当前的金额中添加一枚当前考虑的硬币,从而达到新的金额。这就是为什么要+1。
二、完全平方数
完全平方数就是物品且可以无限使用,凑个正整数n就是背包,问凑满这个背包最少需要多少物品?
2.1、动态规划五部曲
2.1.1、 确定dp数组(dp table)以及下标的含义
- dp[j]表示和为j的完全平方数的最少数量为dp[j]
2.1.2、确定递推公式
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
2.1.3、 dp数组如何初始化
dp[0] = 0;
2.1.4、确定遍历顺序
先遍历背包,再遍历物品。或者先遍历背包,再遍历物品都可以。
2.1.5、计算并返回最终结果
return dp[n];
2.2、代码
class Solution {
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
// 初始化
for (int j = 0; j <= n; j++) {
dp[j] = max;
}
dp[0] = 0;
// 先遍历物品后遍历
for (int i = 1; i * i <= n; i++) {
for (int j = i * i; j <= n; j++) {
if (dp[j - i * i] != max) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
}
return dp[n];
}
}
总结
1.感想
- 疯狂补清明假期拉下的进度。。
2.思维导图
本文思路引用自代码随想录,感谢代码随想录作者。