动态规划经典问题

107 阅读3分钟

动态规划经典问题

动态规划是一种常用的算法设计方法,它通过将复杂的问题分解为多个子问题来解决,并利用这些子问题的解构建原问题的最优解。这种方法尤其适用于具有重叠子问题和最优子结构性质的问题。

01 最长公共子序列(Longest Common Subsequence, LCS)

给定两个字符串 XY,寻找它们的最长公共子序列是动态规划的一个经典应用。一个子序列是在原字符串中保持字符顺序但不必要连续出现的序列。

解决思路

  • 定义状态:设 LCS(X[0..m], Y[0..n]) = result
  • 递推关系式:
    • 如果 X[m] == Y[n],那么 result = LCS(X[0..m-1], Y[0..n-1]) + X[m]
    • 否则, result = max(LCS(X[0..m-1], Y[0..n]), LCS(X[0..m], Y[0..n-1]))

Python实现

def longest_common_subsequence(X, Y):
    m = len(X)
    n = len(Y)

    # 创建一个 (m+1) x (n+1) 的二维数组来存储子问题的解
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    # 填充dp数组
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if X[i - 1] == Y[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    # 返回结果
    return dp[m][n]

02 打家劫舍(House Robber)

给定一个非空整数数组,代表每栋房子的金钱数目。在每个晚上,你可以任意选择一所房屋进行抢劫,但是如果你连续抢劫了两所相邻的房子,则会触发报警系统。你的任务是计算最多可以偷窃到多少金钱。

解决思路

  • 定义状态:设 dp[i] 表示前i个房子中能获得的最大金额。
  • 递推关系式:
    • 不选择当前房屋: dp[i] = dp[i-1]
    • 选择当前房屋: dp[i] = house_value[i] + dp[i-2]

Python实现

def rob(houses):
    n = len(houses)

    if n == 0:
        return 0

    # 如果只有1个房子,直接偷窃它
    if n == 1:
        return houses[0]

    # 初始化前两个状态
    dp = [0] * n
    dp[0], dp[1] = houses[0], max(houses[0], houses[1])

    # 填充dp数组
    for i in range(2, n):
        dp[i] = max(dp[i-1], dp[i-2] + houses[i])

    # 返回结果
    return dp[-1]

03 最小编辑距离(Edit Distance)

给定两个单词 word1word2,找出将 word1 转换为 word2 所需的最少操作次数。允许的操作有:插入一个字符、删除一个字符或替换一个字符。

解决思路

  • 定义状态:设 dp[i][j] 代表将 word1[0..i-1] 转换为 word2[0..j-1] 所需的最少操作次数。
  • 递推关系式:
    • 如果 word1[i-1] == word2[j-1],则不需要任何操作: dp[i][j] = dp[i-1][j-1]
    • 否则,需要进行插入、删除或替换操作之一,取最小值并加1: dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1

Python实现

def edit_distance(word1, word2):
    m = len(word1)
    n = len(word2)

    # 创建一个 (m+1) x (n+1) 的二维数组来存储子问题的解
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    # 初始化第一行和第一列
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j

    # 填充dp数组
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if word1[i - 1] == word2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
            else:
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1

    # 返回结果
    return dp[m][n]

以上就是动态规划的经典问题以及它们的解决思路和实现方法。这些问题是学习和理解动态规划的关键步骤,也是算法面试中常见的题目类型。