上一篇文章:【力扣刷题】Day27——DP专题_塔塔开!!!的博客-CSDN博客
完全背包:每一个物品可以选无限次
题目链接:322. 零钱兑换 - 力扣(LeetCode)
你可以认为每种硬币的数量是无限的。不难联系到完全背包问题。
思路:
/**
f[j]:表示从前i个物品中选 体积恰好为j的所有集合(f[j]:凑足总额为j所需钱币的最少个数为f[j])
属性:Min
状态计算:f[j] = min(f[j], f[j - vi] + 1)
初始化:f[0] = 0,其他的为 +INF
*/
class Solution {
public int coinChange(int[] coins, int amount) {
int[] f = new int[amount + 10];
Arrays.fill(f, 0x3f3f3f3f);
f[0] = 0;
for(int v : coins){
for(int j = 0; j <= amount; j ++){
if(j >= v)
f[j] = Math.min(f[j], f[j - v] + 1);
}
}
return f[amount] == 0x3f3f3f3f ? -1 : f[amount];
}
}
Code
题目链接:518. 零钱兑换 II - 力扣(LeetCode)
这一题和上一题的区别是,上一题是求能凑满j
的所使用的最少零钱个数,本题是凑够j
的所有零钱的方案数(这里的方案指的是组合而不是排列,排列要考虑顺序)
状态表示:所有从前i
个物品中选,且体积恰好为j
的所有集合
属性:count
状态计算:f[j] = f[j] + f[j - vi]
初始化:f[0] = 1
遍历方式:由于是求得组合
数,即不考虑顺序,为此先枚举物品在枚举背包体积
Code
class Solution {
public int change(int amount, int[] coins) {
int[] f = new int[amount + 10];
f[0] = 1;
for(int v : coins){// 物品
for(int j = v; j <= amount; j ++){// 体积
f[j] += f[j - v];
}
}
return f[amount];
}
}
题目链接:377. 组合总和 Ⅳ - 力扣(LeetCode)
完全背包求排列方案数:先枚举体积,在枚举物品
Code
/**
对比零钱兑换II:
本题完全背包求排列方案数:
先枚举背包体积,在枚举物品
*/
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] f = new int[target + 10];
f[0] = 1;
for(int j = 0; j <= target; j ++){// 体积
for(int v : nums){// 物品
if(j >= v){
f[j] += f[j - v];
}
}
}
return f[target];
}
}
题目链接:279. 完全平方数 - 力扣(LeetCode)
Code
思路一:BFS(最短路)
我们把所有小于n
的平方数看成一个图书,求一条从0
开始,逐步遍历图求和,当总和为target
时,此时的这条路径一定是最优的(最短的)——BFS的特点
class Solution {
int target;
List<Integer> squre = new ArrayList<>();
boolean[] st = new boolean[10010];
public int numSquares(int n) {
target = n;
for(int i = 1; i <= n; i ++){
if(i * i <= n){
squre.add(i * i);
}
}
return bfs(0);
}
public int bfs(int sum){
int res = 0;
Queue<Integer> q = new LinkedList<>();
q.offer(0);// 起点入队
while(q.size() != 0){
int len = q.size();
res ++;
for(int i = 0; i < len; i ++){
int t = q.poll();
if(st[t]) continue;// 标记用过
st[t] = true;
// 下一个可能的数
for(int x : squre){
if(t + x == target){
return res;
}
else if(t + x < target){
q.offer(t + x);// 加入队列
}
}
}
}
return res;
}
}
思路二:动态规划(完全背包)
题目的另一个意思是:把1~n
的完全平方数看作是物品,从这些物品中选,每个物品可以先无限次,问恰好凑满背包最少需要多少个物品?
状态表示:f[j]
:从前i
个物品中选,恰好凑满背包容量j
的所需要的最少物品数为f[j]
属性:Min
状态计算:f[j] = min(f[j], f[j - v] + 1)
初始化:f[0] = 0,其他均为+INF
class Solution {
public int numSquares(int n) {
if(n == 1) {// 特判一下
return 1;
}
List<Integer> square = new ArrayList<>();
for(int i = 0; i < n; i ++){
if(i * i <= n){
square.add(i * i);
}
}
int[] f = new int[n + 10];
Arrays.fill(f, 0x3f3f3f3f);
f[0] = 0;
for(int i = 0; i < square.size(); i ++){
int v = square.get(i);
for(int j = v; j <= n; j ++){
f[j] = Math.min(f[j], f[j - v] + 1);
}
}
return f[n];
}
}
题目链接:139. 单词拆分 - 力扣(LeetCode)
Code
思路一:dfs递归回溯枚举所有s
的所有子串,然后判断是否在 wordDict []
中出现过。
超时了!
class Solution {
Set<String> set = new HashSet<>();
public boolean wordBreak(String s, List<String> wordDict) {
for(int i = 0; i < wordDict.size(); i ++) set.add(wordDict.get(i));
return dfs(s, 0);
}
public boolean dfs(String s, int start){
if(start >= s.length()){
return true;
}
for(int i = start; i < s.length(); i ++){
String word = s.substring(start, i + 1);// 枚举s的所有子串
if(check(word) && dfs(s, i + 1)){
return true;
}
}
return false;
}
public boolean check(String word){
if(set.contains(word)){
return true;
}
return false;
}
}
思路二:动态规划,完全背包问题
状态表示:f[i]
: s
中的前i
个字符(长度为i
的子串)是否能由 wordDict []
中的字符串组成
背包:s
字符串的前i
个字符(子串)
物品:wordDict[]
中的字符串
状态计算:f[i] = f[i] || f[i - len]
,其中len
为wordDict[]
中的某一个字符串的长度
初始化:f[0] = true
遍历顺序:既然上述我们可以用dfs回溯枚举所有可能,即为枚举排列类型
,串s
能有排序构成即为一种合理情况,为此是完全背包枚举排列类型——先枚举背包体积(容量)再枚举物品
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
boolean[] f = new boolean[n + 10];
f[0] = true;
for(int i = 1; i <= n; i ++){// 背包容量
for(String word : wordDict){//
int len = word.length();// 体积
if(i >= len && word.equals(s.substring(i - len, i))){// 满足条件才行呀
f[i] = f[i] || f[i - len];
}
}
}
return f[n];
}
}