☰
LeetCode 474题解析:动态规划解决二维背包问题
2026/11/2 5:16:10 网站建设 项目流程

1. 问题背景与题目解析

LeetCode 474题"一和零"是一个经典的动态规划问题,属于中等难度。题目描述如下:

给定一个字符串数组strs和两个整数m和n,你需要找到并返回strs的最大子集的大小,该子集中最多有m个0和n个1。每个字符串中的字符只能是'0'或'1'。

这个问题的核心在于如何在有限的0和1资源下,选择最多的字符串组合。它实际上是经典的"0-1背包问题"的变种,只不过这里的"背包容量"有两个维度:0的数量和1的数量。

2. 解题思路分析

2.1 问题转化

首先我们需要将这个问题转化为更易理解的模型。每个字符串可以看作是一个物品,它有两个属性:

  • 0的个数(重量1)
  • 1的个数(重量2)

我们的目标是选择尽可能多的"物品",使得:

  • 所有选中物品的0的总数 ≤ m
  • 所有选中物品的1的总数 ≤ n

这明显是一个二维费用的背包问题。

2.2 动态规划定义

我们定义一个三维动态规划数组dp,其中:

  • dp[i][j][k]表示在前i个字符串中,使用最多j个0和k个1能够组成的最大子集的大小

状态转移方程为:

  • 如果不选第i个字符串:dp[i][j][k] = dp[i-1][j][k]
  • 如果选第i个字符串:dp[i][j][k] = dp[i-1][j-zero][k-one] + 1 (其中zero和one分别是第i个字符串中0和1的数量)

最终结果是dp[strs.length][m][n]

2.3 空间优化

由于每次状态转移只依赖于前一行,我们可以将三维数组优化为二维数组,节省空间:

  • dp[j][k]表示使用最多j个0和k个1能够组成的最大子集的大小

此时需要从后向前更新dp数组,避免覆盖还未使用的数据。

3. 详细代码实现

3.1 预处理函数

首先我们需要一个辅助函数来计算每个字符串中0和1的数量:

def count_zero_and_one(s): zero = s.count('0') one = len(s) - zero return zero, one

3.2 动态规划解法

完整实现代码如下:

def findMaxForm(strs, m, n): dp = [[0] * (n + 1) for _ in range(m + 1)] for s in strs: zero, one = count_zero_and_one(s) # 从后向前遍历,避免重复计算 for i in range(m, zero - 1, -1): for j in range(n, one - 1, -1): dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1) return dp[m][n]

3.3 复杂度分析

  • 时间复杂度:O(l * m * n),其中l是字符串数组的长度
  • 空间复杂度:O(m * n),使用二维数组存储状态

4. 实际应用与变种

4.1 实际应用场景

这个问题虽然看起来抽象,但实际上有很多现实应用:

  1. 资源分配问题:在有限的两种资源下选择最优的项目组合
  2. 广告投放优化:在预算和展示次数限制下选择最优广告组合
  3. 投资组合选择:在风险和收益的双重限制下选择投资组合

4.2 问题变种

这个问题可以有多种变体:

  1. 每个字符串有不同的价值,求最大价值而非最大数量
  2. 增加第三种限制条件(如字符串长度)
  3. 需要输出具体的字符串组合而不仅仅是数量

5. 常见错误与调试技巧

5.1 常见错误

  1. 初始化错误:dp数组大小应该是(m+1)×(n+1),不是m×n
  2. 遍历顺序错误:必须从后向前更新dp数组,否则会重复计算
  3. 边界条件处理:当m或n为0时的特殊情况需要处理

5.2 调试技巧

  1. 打印中间状态:在循环中打印dp数组,观察状态变化
  2. 小规模测试:先用小的测试用例验证代码正确性
  3. 对比暴力解:对于小规模数据,可以对比暴力解的结果

6. 性能优化建议

  1. 预处理所有字符串的0和1计数,避免重复计算
  2. 根据实际情况,可以先对字符串进行排序(如按长度),可能有助于提前剪枝
  3. 对于特别大的m和n,可以考虑记忆化搜索而非动态规划

7. 扩展思考

这个问题展示了动态规划的灵活性。传统的背包问题只有一个容量限制,而这个问题有两个,体现了动态规划处理多维约束的能力。理解这个问题的解法有助于解决更复杂的资源分配问题。

在实际面试中,面试官可能会追问:

  • 如何修改代码以记录选择的字符串?
  • 如果字符串可以重复选择,如何修改算法?
  • 如何优化算法以处理极大m和n的情况?

掌握这类问题的核心在于理解状态定义和转移方程的本质,而不是死记硬背模板。建议多练习类似的二维费用背包问题,加深理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询