代码随想录算法训练营第四十四天 | 322.零钱兑换,279.完全平方数,139.单词拆分,多重背包理论基础,背包问题总结

322. 零钱兑换

题目讲解:代码随想录
重点:

  1. 理解递推公式及遍历顺序。
  2. 这题求的是最少个数,也就是不论组合还是排列都可以。

思路:

  1. dp数组的含义
// 凑成总金额为j的最少硬币个数
int[] dp = new int[amount + 1];
  1. 递推公式
// 不取当前硬币 或者 取当前硬币,看哪个方法个数最少
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
  1. dp数组的初始化
// 凑成总金额为0的最少硬币个数肯定是0
dp[0] = 0;
// 递推公式是取min,所以其他非0的初始化成最大值
for (int j = 1; j < dp.length; j++) {
   dp[j] = Integer.MAX_VALUE;
}
  1. 遍历顺序
// 因为dp数组存的是最少个数,也就无关排列和组合
// 先遍历物品再遍历背包 或者 先遍历背包再遍历物品都可以
for (int i = 0; i < coins.length; i++)
   for (int j = coins[i]; j <= amount; j++)
  1. 模拟dp数组

public int coinChange(int[] coins, int amount) {
    // 凑成总金额为j的最少硬币个数
    int[] dp = new int[amount + 1];
    // 凑成总金额为0的最少硬币个数肯定是0
    dp[0] = 0;
    // 递推公式是取min,所以其他非0的初始化成最大值
    for (int j = 1; j < dp.length; j++) {
        dp[j] = Integer.MAX_VALUE;
    }
    // 因为dp数组存的是最少个数,也就无关排列和组合
    // 先遍历物品再遍历背包 或者 先遍历背包再遍历物品都可以
    for (int i = 0; i < coins.length; i++) {
        for (int j = coins[i]; j <= amount; j++) {
            // 避免后面整数最大值还要+1
            if (dp[j - coins[i]] != Integer.MAX_VALUE) {
                // 不取当前硬币 或者 取当前硬币,看哪个方法个数最少
                dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
            }
        }
    }
    if (dp[amount] == Integer.MAX_VALUE) {
        return -1;
    }
    return dp[amount];
}

279. 完全平方数

题目讲解:代码随想录
重点:

  1. 理解求数量问题的遍历顺序。
  2. 理解for循环里的i * i

思路:

  1. dp数组的含义
// 和为j的完全平方数的最少数量
int[] dp = new int[n + 1];
  1. 递推公式
// 不用当前完全平方数的数量 和 用当前完全平方数的数量 取最小
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
  1. dp数组的初始化
// 和为0的完全平方数的最少数量为0
dp[0] = 0;
// 递推公式取min, 所以非0初始化为最大值
for (int j = 1; j < dp.length; j++) {
   dp[j] = Integer.MAX_VALUE;
}
  1. 遍历顺序
// 求的是最少数量: 先遍历物品再遍历背包 或者 先遍历背包再遍历物品 都可以
for (int i = 1; i * i <= n; i++)
   for (int j = i * i; j <= n; j++)
  1. 模拟dp数组

public int numSquares(int n) {
    // 和为j的完全平方数的最少数量
    int[] dp = new int[n + 1];
    // 和为0的完全平方数的最少数量为0
    dp[0] = 0;
    // 递推公式取min, 所以非0初始化为最大值
    for (int j = 1; j < dp.length; j++) {
        dp[j] = Integer.MAX_VALUE;
    }
    // 求的是最少数量,先遍历物品再遍历背包 或者 先遍历背包再遍历物品 都可以
    for (int i = 1; i * i <= n; i++) {
        for (int j = i * i; j <= n; j++) {
            // 不用当前完全平方数的数量 和 用当前完全平方数的数量 取最小
            dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
        }
    }
    return dp[n];
}

139. 单词拆分

题目讲解:代码随想录
重点:

  1. 理解递推公式的if判断
  2. 理解dp数组的含义

思路:

  1. dp数组的含义
// 字符串长度为j能否拆分
boolean[] dp = new boolean[s.length() + 1];
  1. 递推公式
// 递推公式
// 1. 判断当前字符串长度能否容纳当前单词
// 2. 判断当前字符串长度减去当前单词长度(上一个子串)是否为true
// 3. 判断当前字符串子串是否等于当前单词
if (j >= wordLen && dp[j - wordLen] && word.equals(s.substring(j - wordLen, j))) {
	dp[j] = true;
	// 当前子串匹配上了, 直接剪枝到下一个子串
	break;
}
  1. dp数组的初始化
// 长度为0, 直接不取任何字典里的单词
dp[0] = true;
  1. 遍历顺序
// 排列问题, 先遍历背包再遍历物品
for (int j = 1; j <= s.length(); j++)
   for (String word : wordDict)
  1. 模拟dp数组

public boolean wordBreak(String s, List<String> wordDict) {
    // 字符串长度为j能否拆分
    boolean[] dp = new boolean[s.length() + 1];
    // 长度为0, 直接不取任何字典里的单词
    dp[0] = true;
    // 排列问题, 先遍历背包再遍历物品
    for (int j = 1; j <= s.length(); j++) {
        for (String word : wordDict) {
            int wordLen = word.length();
            // 递推公式
            // 1. 判断当前字符串长度能否容纳当前单词
            // 2. 判断当前字符串长度减去当前单词长度(上一个子串)是否为true
            // 3. 判断当前字符串子串是否等于当前单词
            if (j >= wordLen && dp[j - wordLen]
                    && word.equals(s.substring(j - wordLen, j))) {
                dp[j] = true;
                // 当前子串匹配上了, 直接剪枝到下一个子串
                break;
            }
        }
    }
    return dp[s.length()];
}

多重背包理论基础

理论讲解:代码随想录
重点:

  1. 多重背包和01背包是非常像的, 为什么和01背包像呢?每件物品最多有i件可用,把i件摊开,其实就是一个01背包问题了。
    在这里插入图片描述
  2. 从代码里可以看出多重背包是01背包里面再加一个for循环遍历一个每种商品的数量
import java.util.Scanner;
class multi_pack{
    public static void main(String [] args) {
        Scanner sc = new Scanner(System.in);
        /**
         * bagWeight:背包容量
         * n:物品种类
         */
        int bagWeight, n;
        //获取用户输入数据,中间用空格隔开,回车键换行
        bagWeight = sc.nextInt();
        n = sc.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) weight[i] = sc.nextInt();
        for (int i = 0; i < n; i++) value[i] = sc.nextInt();
        for (int i = 0; i < n; i++) nums[i] = sc.nextInt();
        
		// 多重背包问题代码如下 
        int[] dp = new int[bagWeight + 1];
        //先遍历物品再遍历背包,作为01背包处理
        for (int i = 0; i < n; i++) {
            for (int j = bagWeight; j >= weight[i]; j--) {
                // **重点:遍历每种物品的个数**
                for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) {
                    dp[j] = Math.max(dp[j], dp[j - k * weight[i]] + k * value[i]);
                }
            }
        }
        System.out.println(dp[bagWeight]);
    }
}

背包问题总结

总结讲解:代码随想录

背包问题的分类及动规五部曲

在这里插入图片描述

  1. dp数组的含义
  2. 递推公式
  3. dp数组的初始化
  4. 确定遍历顺序
  5. 模拟dp数组

不同类型问题的递推公式总结

首先要理解dp数组的含义,理解了就知道递推公式怎么写。

  1. 问能否能装满背包 或者 最多装多少
    dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
  2. 问装满背包有几种方法
    dp[j] += dp[j - nums[i]];
  3. 问背包装满最大价值
    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
  4. 问装满背包所有物品的最小个数
    dp[j] = Math.min(dp[j], dp[j - coins[i]]);

遍历顺序

01背包

分两种情况: dp数组是二维和dp数组是一维:

  1. 二维数组: 先正序遍历物品再正序遍历背包 或者 先正序遍历背包再正序遍历物品 都可以
  2. 一维数组: 先正序遍历物品再倒序遍历背包
完全背包

一维数组分三种情况: 纯完全背包,求组合,求排列

  1. 纯完全背包: 先正序遍历物品再正序遍历背包 或者 先正序遍历背包再正序遍历物品 都可以
  2. 求组合: 先正序遍历物品再正序遍历背包
  3. 求排列: 先正序遍历背包再正序遍历物品

思维导图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值