Skip to content

Commit 80d6b63

Browse files
committed
474.一和零,0-1背包,二三维dp
1 parent 744ccae commit 80d6b63

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

leetcode_Java/DoneTitle.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
448. 找到所有数组中消失的数字
6464
461. 汉明距离
6565
463. 岛屿的周长
66+
474. 一和零
6667
491. 递增子序列
6768
494. 目标和
6869
509. 斐波那契数

leetcode_Java/Solution0474.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// 474. 一和零
2+
3+
4+
/*
5+
动态规划,0-1背包,三维dp数组
6+
1、题目:给你一个二进制字符串数组strs和两个整数m和n。请你找出并返回strs的最大子集的长度,该子集中最多有m个0和n个1。如果x的所有元素也是y的元素,集合x是集合y的子集。
7+
2、题目简化:把总共的 0 和 1 的个数视为背包的容量,每一个字符串视为装进背包的物品。这道题就可以使用 0-1 背包问题的思路完成,这里的目标值是能放进背包的字符串的数量。
8+
3、定义dp数组:dp[k][i][j] 表示字符串数组的 前k个字符串 能够使用最多 i个0 和 j个1 的字符串的最大数量。
9+
4、状态转移方程:
10+
if (i >= zero && j >= one) dp[k][i][j] = Math.max(dp[k - 1][i][j], dp[k - 1][i - zero][j - one] + 1); // 够放,比较“不放”和“放”的最大值
11+
else dp[k][i][j] = dp[k - 1][i][j]; // 不够放,直接取“不放”的值
12+
5、初始化:创建三维dp数组时默认初始化为0,dp[0][i][j] = 0 表示前0个字符串是空字符串,能够使用最多i个0和j个1的字符串的最大数量为0
13+
6、遍历dp数组填表:
14+
第一层遍历字符串数组,k从1开始,因为k为0的情况已经初始化好了,并计算当前字符串0和1的个数
15+
第二层遍历0的个数,i从0开始,因为“0”的个数可能为0
16+
第三次遍历1的个数,j从0开始,因为“1”的个数可能为0
17+
7、返回结果:最后一个状态就是结果
18+
*/
19+
class Solution {
20+
public int findMaxForm(String[] strs, int m, int n) {
21+
int len = strs.length;
22+
int[][][] dp = new int[len + 1][m + 1][n + 1];
23+
for (int k = 1; k <= len; k++) {
24+
int zero = 0, one = 0;
25+
for (char c : strs[k - 1].toCharArray()) {
26+
if (c == '0') zero++;
27+
else one++;
28+
}
29+
for (int i = 0; i <= m; i++) {
30+
for (int j = 0; j <= n; j++) {
31+
if (i >= zero && j >= one) {
32+
dp[k][i][j] = Math.max(dp[k - 1][i][j], dp[k - 1][i - zero][j - one] + 1);
33+
} else {
34+
dp[k][i][j] = dp[k - 1][i][j];
35+
}
36+
}
37+
}
38+
}
39+
return dp[len][m][n];
40+
}
41+
}
42+
43+
44+
/*
45+
动态规划,0-1背包,二维dp数组。状态压缩,空间优化,滚动数组
46+
1、定义dp数组:dp[i][j] 表示能够使用最多 i个0 和 j个1 的字符串的最大数量
47+
2、状态转移方程:dp[i][j] = Math.max(dp[i][j], dp[i - zero][j - one] + 1);
48+
1)等式右侧 dp[i][j] 表示 前k-1个字符串 能够使用最多 i个0 和 j个1 的字符串的最大数量
49+
2)等式右侧 dp[i - zero][j - one] 表示 前k-1个字符串 能够使用最多 i-zero个0 和 j-one个1 的字符串的最大数量,加1 表示在本轮中“放”第k个字符串 刚好使用 i个0 和 j个1 的字符串的最大数量
50+
3)等式左侧 dp[i][j] 表示 前k个字符串 能够使用最多 i个0 和 j个1 的字符串的最大数量
51+
3、初始化:创建二维dp数组时默认初始化为0,dp[0][0] = 0 表示能够使用最多 i个0 和 j个1 的字符串的最大数量为0
52+
4、遍历dp数组填表
53+
1)遍历顺序
54+
① 第一层遍历字符串数组,并计算当前字符串0和1的个数
55+
② 第二层遍历0的个数,倒序遍历,只遍历 i >= zero 的情况
56+
③ 第三层遍历1的个数,倒序遍历,只遍历 j >= one 的情况
57+
2)遍历理解
58+
① 二维数组是滚动数组,每一轮滚动遍历中,未遍历的表示上一轮的旧状态,正在遍历的表示正在计算的状态,遍历过的表示本轮的新状态
59+
② 本轮遍历中,背包容量不够放时,只能选择不放,旧状态就是“不放”,所以只遍历背包容量足够的情况即可(i >= zero && j >= one)
60+
③ 倒序遍历是为了正确使用旧状态。如果是正序遍历就会在前面覆盖旧状态,求新状态时用到了前面被覆盖过的状态,表示的含义是物品重复使用,属于完全背包
61+
5、返回结果:最后一个状态就是结果
62+
*/
63+
class Solution {
64+
public int findMaxForm(String[] strs, int m, int n) {
65+
int[][] dp = new int[m + 1][n + 1];
66+
for (String str : strs) {
67+
int zero = 0, one = 0;
68+
for (char c : str.toCharArray()) {
69+
if (c == '0') zero++;
70+
else one++;
71+
}
72+
for (int i = m; i >= zero; i--) {
73+
for (int j = n; j >= one; j--) {
74+
dp[i][j] = Math.max(dp[i][j], dp[i - zero][j - one] + 1);
75+
}
76+
}
77+
}
78+
return dp[m][n];
79+
}
80+
}

leetcode_Java/Solution0494.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ public int findTargetSumWays(int[] nums, int target) {
9898
1、题目简化:有一些物品,第 i 个物品的重量为nums[i],背包的容量为sumA,求有多少种方式将背包【恰好装满】
9999
2、定义dp数组:dp[j] 表示凑出和为 j 的组合数,即方法数
100100
3、状态转移方程:dp[j] = dp[j] + dp[j - num];
101-
1)一维数组是滚动数组,每一轮滚动遍历中,未遍历的表示上一轮的旧状态,正在遍历的表示正在计算的状态,遍历过的表示本轮的新状态
102-
2)等式右侧 dp[j] 表示前 i-1 个物品凑出和为 j 的组合数,在本轮中表示“不放”第i个物品就凑出和为j的组合数
103-
3)等式右侧 dp[j - num] 表示前 i-1 个物品凑出和为 j-nums[i] 的组合数,在本轮中表示“放”第i个物品刚好凑出和为j的组合数
104-
4)等式左侧 dp[j] 表示前 i 个物品凑出和为 j 的组合数,是由“不放”和“放”两种情况下凑出和为j的组合数相加
105-
5)本轮遍历中,背包容量不够放时,只能选择不放,旧状态就是“不放”,所以只遍历背包容量足够的情况即可(j >= num)
101+
1)等式右侧 dp[j] 表示前 i-1 个物品凑出和为 j 的组合数,在本轮中表示“不放”第i个物品就凑出和为j的组合数
102+
2)等式右侧 dp[j - num] 表示前 i-1 个物品凑出和为 j-nums[i] 的组合数,在本轮中表示“放”第i个物品刚好凑出和为j的组合数
103+
3)等式左侧 dp[j] 表示前 i 个物品凑出和为 j 的组合数,是由“不放”和“放”两种情况下凑出和为j的组合数相加
106104
4、初始化:dp[0] = 1; 装满容量为0的背包,有1种方法,就是装0件物品
107105
5、遍历dp数组填表:先遍历数字,再遍历背包,背包倒序遍历
106+
1)一维数组是滚动数组,每一轮滚动遍历中,未遍历的表示上一轮的旧状态,正在遍历的表示正在计算的状态,遍历过的表示本轮的新状态
107+
2)本轮遍历中,背包容量不够放时,只能选择不放,旧状态就是“不放”,所以只遍历背包容量足够的情况即可(j >= num)
108+
3)倒序遍历是为了正确使用旧状态。如果是正序遍历就会在前面覆盖旧状态,求新状态时用到了前面被覆盖过的状态,表示的含义是物品重复使用,属于完全背包
108109
6、返回结果:最后一个状态就是结果
109110
*/
110111
class Solution {

0 commit comments

Comments
 (0)