今日收获:零钱兑换,完全平方数,单词拆分,多重背包
1. 零钱兑换
思路:完全背包的组合/排列问题,因为组成金额的硬币最小个数都一样。
(1)dp数组表示组成下标为 j 的金额所需的最小硬币数。
(2)初始化:除了dp为0的位置,其余位置都要初始化为整数最大值,否则会递推公式的计算值一直是0。
(3)遍历顺序:因为是组合或排列都可以,所以先遍历物品再背包或者先背包再物品都可以;因为物品可以无限选择,所以背包容量是从小到大。
(3)递推公式:当不选择当前硬币时,只有dp[j-coins[i]]位置不为最大值才成立,说明存在硬币可以组成金额 j 。
(4)判断结果:最后还要将dp[amount]的值和最大值比较,如果不是最大值才说明有硬币可以组成当前的金额。
方法:
class Solution {
public int coinChange(int[] coins, int amount) {
// 完全背包的组合问题
int len=coins.length;
int[] dp=new int[amount+1]; // 组成总金额为i的最少硬币数
// 初始化
dp[0]=0;
for (int i=1;i<amount+1;i++){
dp[i]=Integer.MAX_VALUE; // 其他位置初始化为最大值,否则递归公式计算的值会一直是0
}
for (int i=0;i<len;i++){
for (int j=0;j<amount+1;j++){
// 只有dp[j-coins[i]]不是最大值时,才有选择的可能
if (j>=coins[i]&&dp[j-coins[i]]!=Integer.MAX_VALUE){
dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
}
}
}
return dp[amount]==Integer.MAX_VALUE?-1:dp[amount];
}
}
2. 完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode)
思路:类似于上一题零钱兑换,只不过把零钱换成了从1开始的平方数
方法:
class Solution {
public int numSquares(int n) {
int[] dp=new int[n+1];
int max=Integer.MAX_VALUE;
int sqrt=(int)Math.sqrt(n);
// 初始化
dp[0]=0;
for (int i=1;i<n+1;i++){
dp[i]=max;
}
for (int i=1;i<=sqrt;i++){
for (int j=0;j<n+1;j++){
if (j>=i*i&&dp[j-i*i]!=max){
dp[j]=Math.min(dp[j],dp[j-i*i]+1);
}
}
}
return dp[n];
}
}
3. 单词拆分
思路:完全背包的排列问题。
(1)dp数组的定义:表示到字符串位置为 i 的子串是否能被字典表示。
(2)初始化子串长度为0时为true,其余子串位置均为false。
(3)遍历顺序:先遍历背包再遍历物品,判断物品是否在字典中。
(4)递推公式:如果当前子串在字典中并且上一个位置的子串为true,则当前位置表示的子串也能被字典表示。
方法:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 完全背包的排列问题
// 判断字典中的单词是否能装满目标字符串
boolean[] dp=new boolean[s.length()+1]; // 判断长度为i的字符串是否能由字典组成
// 初始化
for (boolean d:dp){
d=false;
}
dp[0]=true;
// 先遍历背包再遍历物品求排列
// 物品要通过判断子字符串是否存在字典中判断
HashSet<String> set=new HashSet<>(wordDict);
for (int i=1;i<=s.length();i++){
for (int j=0;j<i;j++){
if (set.contains(s.substring(j,i))&&dp[j]){
dp[i]=true;
}
}
}
return dp[s.length()];
}
}
4. 多重背包(了解就好)
概念:每种物品的数量有限制
题目链接:56. 携带矿石资源(第八期模拟笔试) (kamacoder.com)
思路:在遍历物品再倒序遍历背包的循环中,再添加一层循环遍历物品的数量。物品的数量num从1开始,当前背包的容量要大于num*物品重量,价值也要加上num倍
方法:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int C=sc.nextInt();
int N=sc.nextInt();
int[] w=new int[N];
int[] v=new int[N];
int[] k=new int[N];
for (int i=0;i<N;i++){
w[i]=sc.nextInt();
}
for (int i=0;i<N;i++){
v[i]=sc.nextInt();
}
for (int i=0;i<N;i++){
k[i]=sc.nextInt();
}
int[] dp=new int[C+1];
// 先遍历物品再倒序遍历背包
for (int i=0;i<N;i++){
for (int j=C;j>=w[i];j--){
// 遍历物品的数量
for (int num=1;num<=k[i]&&j>=num*w[i];num++){
dp[j]=Math.max(dp[j],dp[j-num*w[i]]+num*v[i]);
}
}
}
System.out.println(dp[C]);
}
}