2021年1月11日 时间都去哪了?
今日计划:整理背包问题的笔记。力扣题目刷到150+。
今日工作:力扣题目刷到150。
1.322. 零钱兑换[课后题]
2.377. 组合总和 Ⅳ[课后题]
3.139. 单词拆分[课后题]
4.494. 目标和[课后题]
今日总结:
虎头蛇尾
新手表到了。真好。
专业的人做专业的事情。被拉去做小学辅导事倍功半、、、
今日语录:
人这一生最好的活法:“没事早点睡,有空多挣钱!
”能跑能跳,家人环绕,兜里有钱,脸上有笑。”
深圳女孩 的解释
转到2021.1.9-2021.1.31的learning record 首页
正文:
呃呃呃。看官方题解吧:
题目要求:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。
如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。
题解:
F(S)设为组成金额 S最少的硬币数,设最后一枚硬币的面值是 C。那么由于问题的最优子结构,转移方程应为:F(S) = F(S - C) + 1。加1是因为增加了一枚硬币。但我们不知道最后一枚硬币的面值是多少,所以我们需要枚举每个硬币面额值,并选择其中的最小值。
边界条件:当S=0时,也就是金额为0,那么F(S)=F(0)=0。遍历完每个硬币面额值,如果没有任何一种硬币组合能组成总金额,则返回 -1。
其实都需要背诵的。
// 递归+记忆化搜索
public class p9_ac7_322 {
public static void main(String[] args) {
int[] coins = new int[]{1, 2, 5};
int amount = 11;
System.out.println(coinChange(coins, amount));
}
public static int coinChange(int[] coins, int amount) {
if(amount<=0){
return 0;
}
int[] memo=new int[amount+1];
return tryCoinChange(coins,amount,memo);
}
private static int tryCoinChange(int[] coins, int amount,int[] memo){
if(amount<0){
return -1;
}
if(amount==0){
return 0;
}
if(memo[amount]!=0){
return memo[amount];
}
int min=Integer.MAX_VALUE;
for(int i=0;i<coins.length;i++){
int res=tryCoinChange(coins,amount-coins[i],memo);
if(res!=-1){
min=Math.min(min,res+1);
}
}
memo[amount]=(min==Integer.MAX_VALUE)?-1:min;
return memo[amount];
}
}
采用自下而上的方式进行思考。仍定义 F(i)为组成金额 i所需最少的硬币数量,假设在计算 F(i)之前,我们已经计算出 F(0)到F(i-1)的答案。 则 F(i)对应的转移方程应为 F(i)=1+min( F(0)到F(i-1) )
public class p9_ac7_322_1 {
public static void main(String[] args) {
int[] coins = new int[]{1, 2, 5};
int amount = 11;
System.out.println(coinChange(coins, amount));
}
private static int coinChange(int[] coins, int amount) {
if (amount <= 0) {
return 0;
}
int[] memo = new int[amount];
return tryCoinChange(coins, amount, memo);
}
private static int tryCoinChange(int[] coins, int amount, int[] memo) {
// 边界条件
if (amount < 0) {
return -1;
}
if (amount == 0) {
return 0;
}
// 记忆化数组
if (memo[amount - 1] != 0) {
return memo[amount - 1];
}
// 业务代码
// min 是最小货币数量
int min = Integer.MAX_VALUE;
// 遍历硬币数组
for (int i = 0; i < coins.length; i++) {
// res 是货币数量
int res = tryCoinChange(coins, amount - coins[i], memo);
if (res != -1) {
min = Math.min(min, res + 1);
}
}
// 这里需要判断,如果min是极大值,则没有找到需求的结果,则返回-1.
// 如果找到了,那么就返回 获得的最小的硬币数量。
memo[amount - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
return memo[amount - 1];
}
}
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
输入数组的每个元素可以使用多次,这一点和「完全背包」问题有点像;
状态:
dp[i]
:对于给定的由正整数组成且不存在重复数字的数组,和为i
的组合的个数。输出:就是最后一个状态
dp[n]
。状态转移方程:dp[i] = sum{dp[i - num] for num in nums and if i >= num}
定义
dp[0] = 1
的,它表示如果nums
里有一个数恰好等于target
,它单独成为 11 种可能。
public class p9_ac7_377 {
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3};
int target = 4;
System.out.println(combinationSum4(nums, target));
}
// 状态转移方程:dp[i]= dp[i - nums[0]] + dp[i - nums[1]] + dp[i - nums[2]] + ... (当 [] 里面的数 >= 0)
// 特别注意:dp[0] = 1,表示,如果那个硬币的面值刚刚好等于需要凑出的价值,这个就成为 1 种组合方案
// 输入非重复的正整数数组,输入目标和
public static int combinationSum4(int[] nums, int target) {
// dp数组从0到target+1
int[] dp = new int[target + 1];
// 这个值被其它状态参考,设置为 1 是合理的
// 在 0 这一点,我们定义 dp[0] = 1 的,它表示如果 nums 里有一个数恰好等于 target,它单独成为 1 种可能。
dp[0] = 1;
for (int i = 1; i <= target; i++) {
for (int j = 0; j < nums.length; j++) {
// i是遍历的target。如果目标值≥此时的数据nums[j]
if (nums[j] <= i) {
// 题目要求是输出组合的个数。i - nums[j]是说存入了nums[j]这个数据,剩余的数据有多少种组合。
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
}
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。字典中出现的单词可以重复使用
dp是布尔值型数组。dp[i],表示字符串 s 前 i个字符组成的字符串 s[0…i-1]是否能被空格拆分成若干个字典中出现的单词。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class p9_ac7_139 {
public static void main(String[] args) {
String s = "applepenapple";
List<String> wordDict = new ArrayList<>();
wordDict.add("apple");
wordDict.add("pen");
System.out.println(wordBreak(s, wordDict));
}
public static boolean wordBreak(String s, List<String> wordDict) {
// HashSet用来 检查一个字符串是否出现在给定的字符串列表里
// wordDictSet的内容是 单词的列表 wordDict
Set<String> wordDictSet = new HashSet(wordDict);
// s.length()+1 dp的索引是从0到s.length()。
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
// s.substring(j, i) 这是一个String的截取字符串的方法,从第j个开始到i个。
// 判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
// 这里使用的是break哦。
break;
}
}
}
return dp[s.length()];
}
}
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。
对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
题解:
(1)动态规划
dp数组需要记录的数据就是具体的方法数。
dp [i] [j]定义为从数组nums中 0 - i 的元素进行加减可以得到 j 的方法数量。
这道题的关键是nums[i]是加还是减。看下面是 j ± nums[ i ] 。可以理解为nums[i]这个元素我可以执行加,还可以执行减,那么我dp[i] [j]的结果值就是加/减之后对应位置的和。
dp[ i ] [ j ] = dp[ i - 1 ] [ j - nums[ i ] ] + dp[ i - 1 ] [ j + nums[ i ] ]
public class p9_ac7_494 {
public static void main(String[] args) {
int[] nums = new int[]{1, 1, 1, 1, 1};
int s = 3;
System.out.println(findTargetSumWays(nums, s));
}
public static int findTargetSumWays(int[] nums, int s) {
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
// 绝对值范围超过了sum的绝对值范围则无法得到
if (Math.abs(s) > Math.abs(sum)) return 0;
int len = nums.length;
// - 0 +
int t = sum * 2 + 1;
int[][] dp = new int[len][t];
// 初始化
if (nums[0] == 0) {
dp[0][sum] = 2;
} else {
dp[0][sum + nums[0]] = 1;
dp[0][sum - nums[0]] = 1;
}
for (int i = 1; i < len; i++) {
for (int j = 0; j < t; j++) {
// 边界
int l = (j - nums[i]) >= 0 ? j - nums[i] : 0;
int r = (j + nums[i]) < t ? j + nums[i] : 0;
dp[i][j] = dp[i - 1][l] + dp[i - 1][r];
}
}
return dp[len - 1][sum + s];
}
}