动态规划
前言
本文为从易到难总结常见的动态规划问题
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阶的方法数之和。
描述图
状态转移方程
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)
背景
给定两个字符串word1和word2,计算将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
总结
通过上述五个例子,我们逐步介绍了动态规划的基本概念、解题思路和具体实现方法。

浙公网安备 33010602011771号