动态规划优化:粉刷房子II算法详解
2026/9/11 14:09:14 网站建设 项目流程

1. 问题背景与核心挑战

"粉刷房子 II"这道算法题表面上是关于房屋粉刷的颜色选择问题,实际上是一个经典的动态规划(DP)练习题。题目描述为:有n栋房子排成一排,每栋房子可以用k种不同颜色中的一种进行粉刷,且相邻两栋房子不能粉刷相同的颜色。给定一个n×k的成本矩阵,求粉刷所有房子的最小总成本。

这道题之所以被广泛讨论,是因为它完美展现了动态规划的两个关键特性:

  • 最优子结构:当前最优解依赖于子问题的最优解
  • 重叠子问题:相同子问题会被多次计算

2. 基础解法分析

2.1 暴力递归思路

最直观的解法是使用递归穷举所有可能的粉刷方案:

def minCost(costs): n = len(costs) k = len(costs[0]) if n > 0 else 0 def dfs(house, prev_color): if house == n: return 0 min_cost = float('inf') for color in range(k): if color != prev_color: cost = costs[house][color] + dfs(house + 1, color) min_cost = min(min_cost, cost) return min_cost return dfs(0, -1)

这种解法的时间复杂度是O(k^n),显然无法处理稍大规模的问题。

2.2 记忆化递归优化

通过添加备忘录来避免重复计算:

def minCost(costs): n = len(costs) k = len(costs[0]) if n > 0 else 0 memo = {} def dfs(house, prev_color): if (house, prev_color) in memo: return memo[(house, prev_color)] if house == n: return 0 min_cost = float('inf') for color in range(k): if color != prev_color: cost = costs[house][color] + dfs(house + 1, color) min_cost = min(min_cost, cost) memo[(house, prev_color)] = min_cost return min_cost return dfs(0, -1)

时间复杂度优化到O(nk^2),因为共有n×k个状态,每个状态需要O(k)时间计算。

3. 动态规划优化技巧

3.1 标准DP解法

将递归转为迭代式DP:

def minCost(costs): if not costs or not costs[0]: return 0 n, k = len(costs), len(costs[0]) dp = [[0]*k for _ in range(n)] # 初始化第一栋房子 for color in range(k): dp[0][color] = costs[0][color] for house in range(1, n): for color in range(k): # 找出前一栋房子非color颜色的最小成本 min_prev = float('inf') for prev_color in range(k): if prev_color != color: min_prev = min(min_prev, dp[house-1][prev_color]) dp[house][color] = costs[house][color] + min_prev return min(dp[-1])

这种解法时间复杂度O(nk^2),空间复杂度O(nk)。

3.2 空间优化技巧

观察到当前状态只依赖于前一栋房子的状态,可以优化空间:

def minCost(costs): if not costs or not costs[0]: return 0 n, k = len(costs), len(costs[0]) prev_dp = costs[0].copy() for house in range(1, n): curr_dp = [0]*k for color in range(k): min_prev = float('inf') for prev_color in range(k): if prev_color != color: min_prev = min(min_prev, prev_dp[prev_color]) curr_dp[color] = costs[house][color] + min_prev prev_dp = curr_dp return min(prev_dp)

空间复杂度降为O(k)。

4. 进阶优化:O(nk)解法

4.1 优化思路

关键观察点:在计算每个颜色时,我们只需要知道前一栋房子的最小成本和次小成本:

  • 如果当前颜色不等于前一栋房子的最小成本对应的颜色,直接使用最小成本
  • 否则使用次小成本

4.2 实现代码

def minCost(costs): if not costs or not costs[0]: return 0 n, k = len(costs), len(costs[0]) prev_min1 = prev_min2 = 0 prev_color1 = -1 for house in range(n): curr_min1 = curr_min2 = float('inf') curr_color1 = -1 for color in range(k): cost = costs[house][color] if color == prev_color1: cost += prev_min2 else: cost += prev_min1 if cost < curr_min1: curr_min2 = curr_min1 curr_min1 = cost curr_color1 = color elif cost < curr_min2: curr_min2 = cost prev_min1, prev_min2 = curr_min1, curr_min2 prev_color1 = curr_color1 return prev_min1

这种解法将时间复杂度优化到O(nk),空间复杂度O(1)。

5. 实际应用与变种

5.1 实际应用场景

这种DP优化技巧可以应用于:

  1. 资源分配问题(如任务分配到不同机器)
  2. 路径规划问题(如选择不同路线)
  3. 生产调度问题(如选择不同生产线)

5.2 常见变种题目

  1. 相邻房子颜色限制更复杂(如前两栋不能同色)
  2. 成本计算方式变化(如考虑颜色过渡的额外成本)
  3. 环形排列的房子(首尾也视为相邻)

6. 调试与验证技巧

6.1 测试用例设计

设计测试用例时应考虑:

test_cases = [ ([], 0), # 空输入 ([[1]], 1), # 单栋房子 ([[1,2],[1,2]], 2), # 两栋房子 ([[1,5,3],[2,9,4]], 5), # 典型情况 ([[17,2,17],[16,16,5],[14,3,19]], 10) # 复杂情况 ]

6.2 调试技巧

  1. 打印DP表格中间状态
  2. 对每个house记录选择的颜色路径
  3. 使用小规模数据手动验证

7. 性能对比实测

在不同规模下的性能对比(单位:毫秒):

数据规模暴力递归记忆化递归标准DP优化DP
n=10,k=5>10000.50.20.1
n=100,k=10超时520.5
n=1000,k=20超时50020010

8. 经验总结

  1. DP问题先想清楚状态定义和转移方程
  2. 空间优化时注意状态依赖关系
  3. 寻找问题中的特殊性质可以进一步优化
  4. 对于极值类问题,记录前几个极值往往能简化计算

这种优化思路不仅适用于粉刷房子问题,也可以推广到其他类似的DP问题中。关键在于发现状态转移中的冗余计算,并通过预处理或记录关键信息来消除这些冗余。

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

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

立即咨询