mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-08-02 20:28:28 +08:00
205 lines
7.0 KiB
Markdown
205 lines
7.0 KiB
Markdown
<p align="center">
|
||
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
|
||
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
|
||
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
|
||
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
|
||
</p>
|
||
<p align="center"><strong>欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||
# 动态规划:一和零!
|
||
|
||
## 474.一和零
|
||
|
||
题目链接:https://leetcode-cn.com/problems/ones-and-zeroes/
|
||
|
||
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
|
||
|
||
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
|
||
|
||
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
|
||
|
||
示例 1:
|
||
|
||
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
|
||
输出:4
|
||
|
||
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
|
||
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
|
||
|
||
示例 2:
|
||
输入:strs = ["10", "0", "1"], m = 1, n = 1
|
||
输出:2
|
||
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
|
||
|
||
提示:
|
||
|
||
* 1 <= strs.length <= 600
|
||
* 1 <= strs[i].length <= 100
|
||
* strs[i] 仅由 '0' 和 '1' 组成
|
||
* 1 <= m, n <= 100
|
||
|
||
## 思路
|
||
|
||
这道题目,还是比较难的,也有点像程序员自己给自己出个脑筋急转弯,程序员何苦为难程序员呢哈哈。
|
||
|
||
来说题,本题不少同学会认为是多重背包,一些题解也是这么写的。
|
||
|
||
其实本题并不是多重背包,再来看一下这个图,捋清几种背包的关系
|
||
|
||

|
||
|
||
多重背包是每个物品,数量不同的情况。
|
||
|
||
**本题中strs 数组里的元素就是物品,每个物品都是一个!**
|
||
|
||
**而m 和 n相当于是一个背包,两个维度的背包**。
|
||
|
||
理解成多重背包的同学主要是把m和n混淆为物品了,感觉这是不同数量的物品,所以以为是多重背包。
|
||
|
||
但本题其实是01背包问题!
|
||
|
||
这不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。
|
||
|
||
开始动规五部曲:
|
||
|
||
1. 确定dp数组(dp table)以及下标的含义
|
||
|
||
**dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]**。
|
||
|
||
2. 确定递推公式
|
||
|
||
dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
|
||
|
||
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
|
||
|
||
然后我们在遍历的过程中,取dp[i][j]的最大值。
|
||
|
||
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
|
||
|
||
此时大家可以回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
|
||
|
||
对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。
|
||
|
||
**这就是一个典型的01背包!** 只不过物品的重量有了两个维度而已。
|
||
|
||
|
||
3. dp数组如何初始化
|
||
|
||
在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中已经讲解了,01背包的dp数组初始化为0就可以。
|
||
|
||
因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。
|
||
|
||
4. 确定遍历顺序
|
||
|
||
在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中,我们讲到了01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!
|
||
|
||
那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。
|
||
|
||
代码如下:
|
||
```C++
|
||
for (string str : strs) { // 遍历物品
|
||
int oneNum = 0, zeroNum = 0;
|
||
for (char c : str) {
|
||
if (c == '0') zeroNum++;
|
||
else oneNum++;
|
||
}
|
||
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
|
||
for (int j = n; j >= oneNum; j--) {
|
||
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
有同学可能想,那个遍历背包容量的两层for循环先后循序有没有什么讲究?
|
||
|
||
没讲究,都是物品重量的一个维度,先遍历那个都行!
|
||
|
||
5. 举例推导dp数组
|
||
|
||
以输入:["10","0001","111001","1","0"],m = 3,n = 3为例
|
||
|
||
最后dp数组的状态如下所示:
|
||
|
||
|
||

|
||
|
||
|
||
以上动规五部曲分析完毕,C++代码如下:
|
||
|
||
```C++
|
||
class Solution {
|
||
public:
|
||
int findMaxForm(vector<string>& strs, int m, int n) {
|
||
vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
|
||
for (string str : strs) { // 遍历物品
|
||
int oneNum = 0, zeroNum = 0;
|
||
for (char c : str) {
|
||
if (c == '0') zeroNum++;
|
||
else oneNum++;
|
||
}
|
||
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
|
||
for (int j = n; j >= oneNum; j--) {
|
||
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
|
||
}
|
||
}
|
||
}
|
||
return dp[m][n];
|
||
}
|
||
};
|
||
```
|
||
|
||
## 总结
|
||
|
||
不少同学刷过这道提,可能没有总结这究竟是什么背包。
|
||
|
||
这道题的本质是有两个维度的01背包,如果大家认识到这一点,对这道题的理解就比较深入了。
|
||
|
||
|
||
|
||
|
||
## 其他语言版本
|
||
|
||
|
||
Java:
|
||
```Java
|
||
class Solution {
|
||
public int findMaxForm(String[] strs, int m, int n) {
|
||
//dp[i][j]表示i个0和j个1时的最大子集
|
||
int[][] dp = new int[m + 1][n + 1];
|
||
int oneNum, zeroNum;
|
||
for (String str : strs) {
|
||
oneNum = 0;
|
||
zeroNum = 0;
|
||
for (char ch : str.toCharArray()) {
|
||
if (ch == '0') {
|
||
zeroNum++;
|
||
} else {
|
||
oneNum++;
|
||
}
|
||
}
|
||
//倒序遍历
|
||
for (int i = m; i >= zeroNum; i--) {
|
||
for (int j = n; j >= oneNum; j--) {
|
||
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
|
||
}
|
||
}
|
||
}
|
||
return dp[m][n];
|
||
}
|
||
}
|
||
```
|
||
|
||
Python:
|
||
|
||
|
||
Go:
|
||
|
||
|
||
|
||
|
||
-----------------------
|
||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
|
||
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
|