"你的背包,让我不再走得缓慢"
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
474. 一和零 题目描述:给你一个二进制字符串数组 和两个整数 和 。请你找出并返回 的 最大子集 的长度,该子集中最多有 个 和 个 。如果 的所有元素也是 的元素,集合 是集合 的子集 。
| 示例 |
|---|
| 输入: 输出: 解释: 最多有 个 和 个 的最大子集是 ,因此答案是 。其他满足题意但较小的子集包括 和 。 不满足题意,因为它含 个 ,大于 的值 。 |
中规中矩的动态规划
1、定义 dp 状态数组
典型 0-1 背包 问题:定义 数组在 索引区间上, 个 和 个 限制下的子集的最大长度为 ,其中 ,,,。
2、定义 dp 状态方程
确定当前的 有 个 和 个 。
-
如果背包的容量不允许更多的 或 ,即 或! ,或者主动舍弃 ,此时有
-
如果背包的容量即允许 个 和 个 ,此时 且 ,此时有
3、确定 dp 初始状态
代表从 中选择元素, 个 和 个 限制下的子集最大长度,故! 恒为 。
题意中规定: 的长度至少为 ,且 至少由 个 或 个 组成,所以如果背包的二维条件都限制为 ,那子集的最大长度恒为 ;如果没有“ 至少由 个 或 个 组成”的限制条件,这里要计算 有 个 和 个 ,如果 且 ,子集的最大长度为 ,否则为 。)
代表从 中选择元素(只能选择 ), 个 和 个 限制下的子集最大长度。这里先要计算 有 个 和 个 ,如果 且 ,子集的最大长度为 ,否则为 。
4、确定遍历顺序
-
第一层遍历从 到
-
第二层遍历从 到
-
第三层遍历从 到
5、确定最终返回值
回归 定义,即返回
6、代码示例
/**
* 空间复杂度 O(l*m*n),其中l是strs数组的长度
* 时间复杂度 O(l*m*n+L),其中L是strs数组所有字符串的长度和
*/
function findMaxForm(strs: string[], m: number, n: number): number {
const len = strs.length;
const dp = Array.from({ length: len },
() => new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)));
for(let i = 0; i < len; i++) {
dp[i][0][0] = 0; // 初始化时已经是0了,为了理解方便,再赋值一遍
}
const { zeros, ones } = findZerosAndOnes(strs[0]);
for (let j = 0; j <= m; j++) {
for (let k = 0; k <= n; k++) {
dp[0][j][k] = (zeros <= j && ones <= k) ? 1 : 0;
}
}
for (let i = 1; i < len; i++) {
const { zeros, ones } = findZerosAndOnes(strs[i]);
for (let j = 0; j <= m; j++) {
for (let k = 0; k <= n; k++) {
if (j >= zeros && k >= ones) {
dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zeros][k - ones] + 1);
} else {
dp[i][j][k] = dp[i - 1][j][k];
}
}
}
}
return dp[len - 1][m][n];
};
function findZerosAndOnes(str: string): { zeros: number, ones: number } {
let zeros = 0;
let ones = 0;
for(let i = 0, len = str.length; i < len; i++) {
if (str[i] === '0') {
zeros += 1;
} else if (str[i] === '1') {
ones += 1;
}
}
return { zeros, ones };
}