Skip to content

Commit 46d23fb

Browse files
committed
背包问题
1 parent f3292e8 commit 46d23fb

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

leetcode_Java/DoneTitle.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
322. 零钱兑换
5959
338. 比特位计数
6060
406. 根据身高重建队列
61+
416. 分割等和子集
6162
437. 路径总和
6263
448. 找到所有数组中消失的数字
6364
461. 汉明距离

leetcode_Java/Solution0322.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ private int dfs(int[] memo, int[] coins, int amount) {
4141

4242

4343
/*
44-
动态规划
44+
动态规划:完全背包,先物品再背包是计算组合,先背包再物品是计算排列,本题物品背包顺序无关,必须正序遍历背包才能重复使用物品
4545
1、题目:给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。你可以认为每种硬币的数量是无限的。
4646
2、题目简化:求凑成总金额amount所需的最少硬币数
4747
3、定义dp数组:dp[i] 表示凑成总金额i所需的最少硬币数
4848
4、状态转移方程:dp[i] = Math.min(dp[i], dp[i-coin]+1); 需要比较多种硬币情况下的最小值
4949
5、初始化:
5050
dp[i]=amount+1; 填充一个不可能的硬币数,使得不影响计算,也方便状态转移时取最小值
5151
dp[0]=0; 表示凑成总金额0所需的最少硬币数为0,方便后面推导
52-
6、遍历dp数组填表:一个for循环遍历dp数组的未知位置,另一个for循环遍历硬币数组,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
52+
6、遍历dp数组填表:第一个for循环遍历dp数组的未知位置,第二个for循环遍历硬币数组,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
5353
7、返回结果:最后一个状态就是结果
5454
*/
5555
class Solution {
@@ -67,4 +67,22 @@ public int coinChange(int[] coins, int amount) {
6767
}
6868
return (dp[amount] == amount + 1) ? -1 : dp[amount];
6969
}
70+
}
71+
72+
73+
/*
74+
其他同上。遍历dp数组填表:第一个for循环遍历硬币数组,第二个for循环遍历dp数组的未知位置,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
75+
*/
76+
class Solution {
77+
public int coinChange(int[] coins, int amount) {
78+
int[] dp = new int[amount + 1];
79+
Arrays.fill(dp, amount + 1);
80+
dp[0] = 0;
81+
for (int coin : coins) {
82+
for (int i = coin; i <= amount; i++) {
83+
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
84+
}
85+
}
86+
return (dp[amount] == amount + 1) ? -1 : dp[amount];
87+
}
7088
}

leetcode_Java/Solution0416.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// 416. 分割等和子集
2+
3+
4+
/*
5+
动态规划:0-1背包,必须先物品再背包,且逆序遍历背包
6+
1、题目:给你一个只包含正整数的非空数组 nums。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
7+
2、题目简化:求数组元素能否凑成和为sum(nums)/2
8+
3、数据校验:数组长度小于2、总和除以2余数不为0、元素最大值大于目标值,则不能分割
9+
4、定义dp数组:dp[i] 表示数组元素能否凑成和为 i
10+
5、状态转移方程:dp[i] = dp[i] || dp[i - num];
11+
6、初始化:dp[0] = true; 表示数组元素能否凑成和为0,方便后面推导
12+
7、遍历dp数组填表:第一个for循环遍历数组元素,第二个for循环逆序遍历dp数组的未知位置,根据条件获取dp数组的已知位置,
13+
根据状态转移方程取已知结果汇总计算未知结果。逆序遍历是因为元素只用一次,如果正序遍历会导致元素重复使用。
14+
8、返回结果:最后一个状态就是结果
15+
*/
16+
class Solution {
17+
public boolean canPartition(int[] nums) {
18+
if (nums.length < 2) {
19+
return false;
20+
}
21+
int sum = 0, maxNum = 0;
22+
for (int num : nums) {
23+
sum += num;
24+
maxNum = Math.max(maxNum, num);
25+
}
26+
if (sum % 2 != 0) {
27+
return false;
28+
}
29+
int target = sum / 2;
30+
if (maxNum > target) {
31+
return false;
32+
}
33+
boolean[] dp = new boolean[target + 1];
34+
dp[0] = true;
35+
for (int num : nums) {
36+
for (int i = target; i >= num; i--) {
37+
dp[i] |= dp[i - num];
38+
}
39+
}
40+
return dp[target];
41+
}
42+
}
43+
44+
45+
/*
46+
1、问题转化:一堆重量为nums[i],价值也为nums[i]的物品中,能否恰好装满容量为target的背包
47+
2、定义dp数组:dp[i] 表示容量为i的背包最多能装的物品价值
48+
3、状态转移方程:dp[i] = Math.max(dp[i], dp[i - num] + num);
49+
↑ ↑ ↑
50+
新状态,容量为i时最大价值 旧状态,不取num的价值 取num的价值
51+
4、初始化:初始化时默认都为0
52+
5、遍历dp数组填表:第一个for循环遍历数组元素,第二个for循环逆序遍历dp数组的未知位置,根据条件获取dp数组的已知位置,
53+
根据状态转移方程取已知结果汇总计算未知结果。逆序遍历是因为元素只用一次,如果正序遍历会导致元素重复使用。
54+
6、返回结果:判断最后一个状态的值是否为目标值
55+
*/
56+
class Solution {
57+
public boolean canPartition(int[] nums) {
58+
if (nums.length < 2) {
59+
return false;
60+
}
61+
int sum = 0, maxNum = 0;
62+
for (int num : nums) {
63+
sum += num;
64+
maxNum = Math.max(maxNum, num);
65+
}
66+
if (sum % 2 != 0) {
67+
return false;
68+
}
69+
int target = sum / 2;
70+
if (maxNum > target) {
71+
return false;
72+
}
73+
int[] dp = new int[target + 1];
74+
for (int num : nums) {
75+
for (int i = target; i >= num; i--) {
76+
dp[i] = Math.max(dp[i], dp[i - num] + num);
77+
}
78+
}
79+
return dp[target] == target;
80+
}
81+
}

leetcode_Java/Solution0518.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

33

44
/*
5-
动态规划:
5+
动态规划:完全背包,先物品再背包是计算组合,先背包再物品是计算排列,必须正序遍历背包才能重复使用物品
66
1、题目:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。
77
2、题目简化:求凑成总金额amount的硬币组合数
88
3、定义dp数组:dp[i] 表示凑成总金额i的硬币组合数
99
4、状态转移方程:dp[i] += dp[i-coin],即所有dp[i-coin]相加
1010
5、初始化:dp[0] = 1,表示凑成总金额0的硬币组合数为1,方便后面推导
11-
6、遍历dp数组填表:一个for循环遍历dp数组的未知位置,另一个for循环遍历硬币数组,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
12-
1)外层for循环遍历硬币,内层for遍历金额,这种遍历顺序中dp[i]里计算的是组合数
13-
2)外层for循环遍历金额,内层for遍历硬币,这种遍历顺序中dp[i]里计算的是排列数
11+
6、遍历dp数组填表:第一个for循环遍历硬币数组,第二个for循环遍历dp数组的未知位置,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
1412
7、返回结果:最后一个状态就是结果
1513
*/
1614
class Solution {

0 commit comments

Comments
 (0)