返回

粉刷房子:深入浅出理解动态规划状态机

后端

前言

在算法和数据结构的学习中,动态规划 (DP) 是一个必不可少的概念。它是一种解决优化问题的强大方法,通过将问题分解成子问题并存储子问题的解决方案,从而避免重复计算。在本文中,我们将结合动态规划和状态机思想,深入浅出地解析一道经典的 LeetCode 题 - 「粉刷房子」。

问题分析

「粉刷房子」问题如下:

有 n 个房子排成一排,每个房子都可以被粉刷成红色、蓝色或绿色。粉刷每个房子需要的花费如下:

粉刷红色:costs[0][i]
粉刷蓝色:costs[1][i]
粉刷绿色:costs[2][i]

限制条件:

  • 相邻的房子不能被粉刷成相同颜色。
  • 求粉刷所有房子最小的总花费。

动态规划状态机

为了解决这个问题,我们可以使用动态规划状态机的方法。具体来说,我们可以定义一个二维数组 dp,其中 dp[i][j] 表示粉刷前 i 个房子,并且第 i 个房子被粉刷成颜色 j 时的最小总花费。

状态转移方程

根据上述定义,我们可以推导出状态转移方程:

dp[i][0] = min(dp[i-1][1], dp[i-1][2]) + costs[0][i]
dp[i][1] = min(dp[i-1][0], dp[i-1][2]) + costs[1][i]
dp[i][2] = min(dp[i-1][0], dp[i-1][1]) + costs[2][i]

其中:

  • i 表示当前正在粉刷的房子编号。
  • j 表示当前正在粉刷的颜色(0 表示红色,1 表示蓝色,2 表示绿色)。
  • costs[j][i] 表示粉刷第 i 个房子为颜色 j 的花费。
  • min(...) 函数返回最小值。

初始化

对于基础情况,我们可以初始化 dp 数组的第一行:

dp[0][0] = costs[0][0]
dp[0][1] = costs[1][0]
dp[0][2] = costs[2][0]

递推

从第二行开始,我们可以根据状态转移方程递推计算 dp 数组的剩余部分:

def minCost(costs):
  n = len(costs)
  dp = [[0] * 3 for _ in range(n+1)]
  
  dp[0][0] = costs[0][0]
  dp[0][1] = costs[1][0]
  dp[0][2] = costs[2][0]
  
  for i in range(1, n):
    dp[i][0] = min(dp[i-1][1], dp[i-1][2]) + costs[0][i]
    dp[i][1] = min(dp[i-1][0], dp[i-1][2]) + costs[1][i]
    dp[i][2] = min(dp[i-1][0], dp[i-1][1]) + costs[2][i]
  
  return min(dp[n-1])

代码示例

以下是使用 Python 实现的代码示例:

def minCost(costs):
  # 初始化 dp 数组
  n = len(costs)
  dp = [[0] * 3 for _ in range(n+1)]
  
  # 初始化第一行
  dp[0][0] = costs[0][0]
  dp[0][1] = costs[1][0]
  dp[0][2] = costs[2][0]
  
  # 递推计算剩余部分
  for i in range(1, n):
    dp[i][0] = min(dp[i-1][1], dp[i-1][2]) + costs[0][i]
    dp[i][1] = min(dp[i-1][0], dp[i-1][2]) + costs[1][i]
    dp[i][2] = min(dp[i-1][0], dp[i-1][1]) + costs[2][i]
  
  # 返回最小总花费
  return min(dp[n-1])

# 示例输入
costs = [[17,2,17],[16,16,5],[14,3,19]]

# 调用函数并打印结果
result = minCost(costs)
print(result)  # 输出:5

总结

通过将动态规划和状态机思想结合起来,我们可以有效地解决「粉刷房子」问题。本文从问题分析到代码实现,循序渐进地讲解了这一经典题目,希望能够帮助读者深入理解动态规划的精髓。在实际工作中,掌握动态规划技术对于解决各类优化问题至关重要,希望本文能够为读者的算法学习之路增添一抹亮色。