动态规划

前言

本文为从易到难总结常见的动态规划问题


1. 斐波那契数列

背景

斐波那契数列是一个经典的递归问题,定义如下:

  • F(0) = 0
  • F(1) = 1
  • 对于n >= 2, F(n) = F(n-1) + F(n-2)

解题思路

直接递归求解斐波那契数列会导致大量的重复计算,时间复杂度为O(2^n)。使用动态规划可以将时间复杂度优化到O(n),通过保存中间结果避免重复计算。

状态转移方程

dp[i] = dp[i-1] + dp[i-2]

Python实现

def fibonacci(n):
    if n <= 1:
        return n

    # 初始化dp数组
    dp = [0] * (n + 1)
    dp[1] = 1

    # 自底向上计算
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

# 测试
print(fibonacci(10))  # 输出55

2. 爬楼梯问题

背景

假设你正在爬楼梯。需要n步才能到达楼顶。每次你可以爬1步或2步。问有多少种不同的方法可以爬到楼顶?

解题思路

这个问题与斐波那契数列非常相似。到达第n阶的方法数等于到达第n-1阶和第n-2阶的方法数之和。

描述图

graph TD 0-->1 0-->2 1-->2.1 1-->3 2-->3.1 2-->4 2.1-->3.2 2.1-->4.1 3-->4.2 3.1-->4.3 3.2-->4.4

状态转移方程

dp[i] = dp[i-1] + dp[i-2]

Python实现

def climbStairs(n):
    if n == 1:
        return 1
    if n == 2:
        return 2

    dp = [0] * (n + 1)
    dp[1], dp[2] = 1, 2

    for i in range(3, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

# 测试
print(climbStairs(4))  # 输出5

3. 最大子数组和问题(Kadane's Algorithm)

背景

给定一个整数数组nums,找到其中连续子数组的最大和。

解题思路

我们可以通过动态规划来解决这个问题。设dp[i]表示以第i个元素结尾的最大子数组和,则有:

  • 如果dp[i-1] > 0,则dp[i] = dp[i-1] + nums[i]
  • 否则,dp[i] = nums[i]

状态转移方程

dp[i] = max(nums[i], dp[i-1] + nums[i])

Python实现

def maxSubArray(nums):
    if not nums:
        return 0

    # 初始化dp数组
    dp = [0] * len(nums)
    dp[0] = nums[0]
    max_sum = dp[0]

    for i in range(1, len(nums)):
        dp[i] = max(nums[i], dp[i - 1] + nums[i])
        max_sum = max(max_sum, dp[i])

    return max_sum

# 测试
print(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]))  # 输出6

4. 0/1 背包问题

背景

给定一个容量为W的背包和一组物品,每个物品有一个重量weights[i]和价值values[i]。在不超过背包容量的前提下,选择一些物品使得总价值最大。

解题思路

我们可以用二维动态规划表dp[i][w]来表示前i个物品中总重量不超过w时的最大价值。

  • 如果不选择第i个物品:dp[i][w] = dp[i-1][w]
  • 如果选择第i个物品:dp[i][w] = dp[i-1][w-weights[i]] + values[i]

取两者中的较大值作为dp[i][w]的值。

状态转移方程

dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i]] + values[i])

Python实现

def knapsack(weights, values, capacity):
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(1, capacity + 1):
            if weights[i - 1] <= w:
                dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])
            else:
                dp[i][w] = dp[i - 1][w]

    return dp[n][capacity]

# 测试
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 5
print(knapsack(weights, values, capacity))  # 输出7

5. 编辑距离(Edit Distance)

背景

给定两个字符串word1word2,计算将word1转换成word2所需的最少操作次数。允许的操作包括插入、删除和替换一个字符。

解题思路

我们可以用二维动态规划表dp[i][j]来表示将word1的前i个字符转换为word2的前j个字符所需的最少操作次数。

  • 如果word1[i-1] == word2[j-1],则dp[i][j] = dp[i-1][j-1]
  • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1(分别对应删除、插入、替换操作)

状态转移方程

dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 (如果word1[i-1] != word2[j-1]

Python实现

def minDistance(word1, word2):
    m, n = len(word1), len(word2)
    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]

# 测试
print(minDistance("horse", "ros"))  # 输出3

总结

通过上述五个例子,我们逐步介绍了动态规划的基本概念、解题思路和具体实现方法。

posted @ 2025-03-12 17:21  玉米面手雷王  阅读(9)  评论(0)    收藏  举报