动态规划(Dynamic Programming,简称 DP)是一种在数学、管理科学、经济学、计算机科学等领域中广泛使用的算法设计技术。它通过将复杂问题分解为更简单的子问题,并通过存储子问题的解来避免重复计算,从而高效地解决问题。本文将深入探讨动态规划的基本原理、核心思想、应用实例以及如何设计动态规划算法。
动态规划是一种自底向上的算法设计方法,用于解决具有重叠子问题和最优子结构的优化问题。它的核心思想是将一个复杂问题分解为多个相互关联的子问题,通过求解这些子问题并存储其解,最终构建出原问题的最优解。
动态规划的名称来源于其“动态”地解决问题的过程——通过逐步构建解的过程,而不是一次性求解。
F(n) = F(n-1) + F(n-2)
,其中 F(n)
的值依赖于 F(n-1)
和 F(n-2)
的值。因此,斐波那契数列问题具有最优子结构。
F(5)
会多次计算 F(3)
和 F(2)
,这些子问题是重叠的。
dp[i]
,表示第 i
个斐波那契数。状态转移方程为 dp[i] = dp[i-1] + dp[i-2]
。
dp[i]
)来表示问题的某个阶段的解。
斐波那契数列
问题描述:计算第 n
个斐波那契数。
状态定义:dp[i]
表示第 i
个斐波那契数。
状态转移方程:dp[i] = dp[i-1] + dp[i-2]
。
初始条件:dp[0] = 0, dp[1] = 1
。
代码实现:
Python复制
def fibonacci(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[0], dp[1] = 0, 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
背包问题
问题描述:给定一组物品,每个物品有重量和价值,确定在不超过背包容量的情况下,背包中物品的最大价值。
状态定义:dp[i][j]
表示前 i
个物品在容量为 j
的背包中的最大价值。
状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
初始条件:dp[0][j] = 0
(没有物品时价值为 0)。
代码实现:
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 j in range(1, capacity + 1):
if weights[i - 1] <= j:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
else:
dp[i][j] = dp[i - 1][j]
return dp[n][capacity]
最长递增子序列(LIS)
问题描述:给定一个序列,找到其中最长的递增子序列的长度。
状态定义:dp[i]
表示以第 i
个元素结尾的最长递增子序列的长度。
状态转移方程:
dp[i] = max(dp[j] + 1) for all j < i and arr[j] < arr[i]
初始条件:dp[i] = 1
(每个元素自身可以构成长度为 1 的递增子序列)。
代码实现:
Python复制
def longest_increasing_subsequence(arr):
n = len(arr)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if arr[i] > arr[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
编辑距离(Levenshtein Distance)
问题描述:计算将一个字符串转换为另一个字符串所需的最少操作次数(插入、删除、替换)。
状态定义:dp[i][j]
表示将字符串 s1
的前 i
个字符转换为字符串 s2
的前 j
个字符所需的最少操作次数。
状态转移方程:
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + cost)
其中,cost
为 0(字符相同)或 1(字符不同)。
初始条件:dp[0][j] = j
和 dp[i][0] = i
。
代码实现:
Python复制
def edit_distance(s1, s2):
m, n = len(s1), len(s2)
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
for i in range(1, m + 1):
for j in range(1, n + 1):
cost = 0 if s1[i - 1] == s2[j - 1] else 1
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)
return dp[m][n]
优点:
缺点: