322. 零钱兑换
题目链接:322. 零钱兑换
文档讲解:代码随想录
状态:能想到凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],但没想到加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])
思路:
凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])
所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。
递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
注意事项:
考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
然后凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
题解:
public int coinChange(int[] coins, int amount) {// 创建一个数组dp,大小为amount + 1,用于存储到达每个金额的最小硬币数int[] dp = new int[amount + 1];// 将dp数组初始化为一个较大的值,表示这些金额暂时无法通过给定的硬币组成Arrays.fill(dp, Integer.MAX_VALUE);// 如果金额为0,则不需要任何硬币dp[0] = 0;// 遍历每一种硬币面值for (int coin : coins) {// 对于每一种硬币,从它的面值开始,直到amount,更新dp数组for (int j = coin; j <= amount; j++) {// 如果使用当前硬币可以减少硬币数,则更新dp[j]if (dp[j - coin] != Integer.MAX_VALUE) {dp[j] = Math.min(dp[j], dp[j - coin] + 1);}}}// 如果dp[amount]仍为初始值,则表示无法用给定的硬币凑出amount,返回-1return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
279.完全平方数
题目链接:279.完全平方数
文档讲解:代码随想录
状态:做过上道题后感觉还行
思路:
dp[j]:和为j的完全平方数的最少数量为dp[j]
dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。
此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
题解:
public int numSquares(int n) {// 创建一个大小为n + 1的数组dp,用于存储到达每个数所需的最少完全平方数数量int[] dp = new int[n + 1];// 将dp数组初始化为一个较大的值,表示这些数暂时无法通过完全平方数组成Arrays.fill(dp, Integer.MAX_VALUE);// 如果数字为0,则不需要任何完全平方数dp[0] = 0;// 遍历所有可能的完全平方数for (int i = 1; i <= Math.sqrt(n); i++) {// 对于每一个完全平方数,从它的值开始,直到n,更新dp数组for (int j = i * i; j <= n; j++) {// 如果使用当前完全平方数可以减少完全平方数的数量,则更新dp[j]dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}// 返回dp[n],即为达到数字n所需的最少完全平方数数量return dp[n];
}
139.单词拆分
题目链接:139.单词拆分
文档讲解:代码随想录
状态:不会
思路:
单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。
拆分时可以重复使用字典中的单词,说明就是一个完全背包!
dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true(j < i )。
所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
从递推公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递推的根基,dp[0]一定要为true,否则递推下去后面都都是false了。
题解:
public boolean wordBreak(String s, List<String> wordDict) {// 将wordDict转换为一个HashSet,便于查找HashSet<String> set = new HashSet<>(wordDict);// 动态规划数组boolean[] dp = new boolean[s.length() + 1];dp[0] = true;// 遍历s的每个子串for (int i = 1; i <= s.length(); i++) {for (int j = 0; j < i; j++) {// 如果子串s[j:i]在字典中,且dp[j]为true,则dp[i]设为trueif (dp[j] && set.contains(s.substring(j, i))) {dp[i] = true;break;}}}// 返回结果return dp[s.length()];}