代码随想录算法训练营Day43|动态规划part06

113 阅读8分钟

LeetCode 322 零钱兑换

题目链接:leetcode.cn/problems/co…

文档讲解:programmercarl.com/0322.零钱兑换.h…

视频讲解:www.bilibili.com/video/BV14K…

思路

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

由于硬币数量是无限的,所以本题是完全背包问题。且本题不考虑顺序,所以是求组合,应该外层遍历物品,内层遍历背包

考虑动态规划五部曲:

  1. dp数组和下标含义:dp[j]为容量为装满容量为j的包,所需要的最少个数
  2. 确定递推公式:dp[j] = min(dp[j], dp[j-coins[i]]+1) 。由于当第二个值不存在时,不需要修改dp,内层遍历直接从coins[i]开始即可。如果存在,也需要看是否为初始值,如果是初始值说明没有方法通过第二个方法填满,仍然保留原值。
  3. dp数组如何初始化:初始化dp[0]为0,别的初始值需要不能在递推中覆盖别的值,所以应该是最大的数Integer_MAX
  4. 确定遍历顺序:从左到右
  5. 举例推导dp数组:

解法

class Solution {
	public int coinChange(int[] coins, int amount) {	
		int[] dp = new int[amount+1];		
		dp[0] = 0;		
		for (int i = 1; i < dp.length; i++) {		
			dp[i] = Integer.MAX_VALUE;		
		}		
		for (int i = 0; i < coins.length; i++) {		
			for (int j = coins[i]; j <= amount; j++) {			
				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];	
	}
}

LeetCode 279 完全平方数

题目链接:leetcode.cn/problems/pe…

文档讲解:programmercarl.com/0279.完全平方数.…

视频讲解:www.bilibili.com/video/BV12P…

思路

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

本题也不考虑顺序,所以是组合问题,外层遍历完全平方数,内层遍历容量。为了让外层遍历有一个尽头,提前计算n的平方根得到最大的完全平方数。 考虑动态规划五部曲:

  1. dp数组和下标含义:dp[j]为和为j的,所需要的完全平方数最少个数
  2. 确定递推公式:dp[j] = min(dp[j], dp[j-i*i]+1) 。由于当第二个值不存在时,不需要修改dp,内层遍历直接从i*i开始即可。如果存在,也需要看是否为初始值,如果是初始值说明没有方法通过第二个方法填满,仍然保留原值。
  3. dp数组如何初始化:初始化dp[0]为0,别的初始值需要不能在递推中覆盖别的值,所以应该是最大的数Integer_MAX
  4. 确定遍历顺序:从左到右
  5. 举例推导dp数组:

解法

class Solution {
	public int numSquares(int n) {	
		int[] dp = new int[n+1];		
		int maxNum = (int)Math.sqrt(n);		
		dp[0] = 0;		
		for (int i = 1; i < dp.length; i++) {		
			dp[i] = Integer.MAX_VALUE;		
		}		
		for (int i = 1; i <= maxNum; i++) {		
			int num = i * i;			
			for (int j = num; j < dp.length; j++) {			
				if (dp[j-num] != Integer.MAX_VALUE) {				
					dp[j] = Math.min(dp[j], dp[j-num]+1);				
				}			
			}		
		}		
		if (dp[n] == Integer.MAX_VALUE) {		
			return -1;		
		}		
		return dp[n];	
	}
}

LeetCode 139 单词拆分

题目链接:leetcode.cn/problems/wo…

文档讲解:programmercarl.com/0139.单词拆分.h…

视频讲解:www.bilibili.com/video/BV1pd…

思路

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

由于单词可以不按顺序出现,所以应该内层遍历字典里的单词,外层遍历字符串s

考虑动态规划五部曲:

  1. dp数组和下标含义:dp[i]为能否拼接出s中长度为i的前缀,类型为布尔
  2. 确定递推公式:dp[i] = dp[i] || dp[i-wordDict[j].length] 。仅当wordDict[j]匹配上s[0~i]的相同长度后缀时可以使用第二个值。
  3. dp数组如何初始化:dp[0]为true,其余均初始化为false
  4. 确定遍历顺序:从左到右
  5. 举例推导dp数组:

解法

class Solution {
	public boolean wordBreak(String s, List<String> wordDict) {	
		boolean[] dp = new boolean[s.length()+1];		
		dp[0] = true;		
		for (int i = 1; i < dp.length; i++) {		
			dp[i] = false;		
		}		
		for (int i = 0; i < dp.length; i++) {		
			for (int j = 0; j < wordDict.size(); j++) {			
				int strLen = wordDict.get(j).length();				
				if (i >= strLen && s.substring(0, i).endsWith(wordDict.get(j))) {				
					dp[i] = dp[i - strLen] || dp[i];				
				}			
			}		
		}		
		for (boolean b : dp) {		
			System.out.println(b);		
		}		
		return dp[s.length()];	
	}
}

时间复杂度:O(n3)O(n^3),因为返回s的子串需要的复杂度是O(n)O(n) 空间复杂度:O(n)O(n)

多重背包 kama 56 携带矿石资源

题目链接:kamacoder.com/problempage…

文档讲解:programmercarl.com/0139.单词拆分.h…

视频讲解:www.bilibili.com/video/BV1pd…

多重背包知识

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

多重背包和01背包是非常像的, 为什么和01背包像呢?

每件物品最多有Mi件可用,把Mi件摊开,其实就是一个01背包问题了。

思路

本题是组合问题,外层循环物品,内层循环背包容量。 考虑动态规划五部曲:

  1. dp数组和下标含义:dp[j]为装容量为j的背包,能达到的最大价值
  2. 确定递推公式:dp[j] = max(dp[j], dp[j-weight[i]]+value[i])
  3. dp数组如何初始化:都初始化为0
  4. 确定遍历顺序:从右到左
  5. 举例推导dp数组:

解法

public class Main {
	public static void main (String[] args) {	
		Scanner scanner = new Scanner(System.in);				  		
		int capacity = scanner.nextInt();		
		int n = scanner.nextInt();		
		int[] weight = new int[n];		
		for (int i = 0; i < n; i++) {		
			weight[i] = scanner.nextInt();		
		}		
		int[] value = new int[n];		
		for (int i = 0; i < n; i++) {		
			value[i] = scanner.nextInt();		
		}		
		int[] count = new int[n];		
		for (int i = 0; i < n; i++) {		
			count[i] = scanner.nextInt();		
		}				  
		
		int[] dp = new int[capacity+1];		
		for (int i = 0; i < n; i++) {		
			for (int counter = 0; counter < count[i]; counter++) {			
				for (int j = capacity; j >= weight[i]; j--) {				
					dp[j] = Math.max(dp[j], dp[j-weight[i]]+value[i]);				
				}			
			}		
		}		
		System.out.println(dp[capacity]);	
	}
}

背包问题总结

回顾不同背包问题的关系:

Pasted image 20250226105040.png

结合动态规划五部曲:

  1. dp数组和下标含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组 可以看出不同的背包问题在递推公式遍历顺序上有明显的区分。

递推公式

问能否装满或最多装多少,递推公式如dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])。题目有:

  • [[代码随想录算法训练营Day39|动态规划part03-背包问题#LeetCode 416 分割等和子集]]
  • [[代码随想录算法训练营Day41|动态规划part04#LeetCode 1049 最后一块石头的重量 II]] 问装满背包有几种方法,递推公式如dp[j] = dp[j] + dp[j-nums[i]]。题目有:
  • [[代码随想录算法训练营Day41|动态规划part04#LeetCode 494 目标和]]
  • [[代码随想录算法训练营Day42|动态规划part05-完全背包#LeetCode 518 零钱兑换II]]
  • [[代码随想录算法训练营Day42|动态规划part05-完全背包#LeetCode 377 组合总和 Ⅳ]]
  • [[代码随想录算法训练营Day42|动态规划part05-完全背包#kama 57 爬楼梯 (进阶)]] 问背包装满的最大价值,递推公式如dp[j] = max(dp[j], dp[j-weight[i]] + value[i])。题目有:
  • [[代码随想录算法训练营Day41|动态规划part04#LeetCode 474 一和零]] 问装满背包所用的最少个数,递推公式如dp[j] = min(dp[j], dp[j-nums[i]]+1)。题目有:
  • [[代码随想录算法训练营Day43|动态规划part06#LeetCode 322 零钱兑换]]
  • [[代码随想录算法训练营Day43|动态规划part06#LeetCode 279 完全平方数]]

遍历顺序

01背包

  • 二维dp数组:可以先遍历背包,也可以先遍历物品
  • 一维dp数组:外层遍历物品,内层遍历背包。且背包容量从大到小遍历

完全背包

  • 纯完全背包:可以先遍历物品,也可以先遍历背包
  • 求组合数的背包问题:外层遍历物品,内层遍历背包
  • 求排列数的背包问题:外层遍历背包,内层遍历物品

今日收获总结

今日学习4小时,总结背包问题的重点其实是遍历顺序。因为递推公式都差别不大,不易出错。