动态规划的基本解题步骤:
-
定义状态:首先需要定义状态,通常是一个数组或矩阵,表示子问题的解。
- 状态定义的核心是明确每个子问题的目标,比如最小/最大值、总和等。
-
状态转移方程:根据问题的结构,推导出当前状态与前一状态之间的关系。这个公式是动态规划的核心。
-
初始化:根据问题的边界条件,初始化状态数组。
-
计算顺序:按照一定的顺序(通常是从小问题到大问题),逐步计算出最终答案。
-
返回结果:根据状态数组(或表格)中最终的值来返回问题的解。
动态规划常见问题类型:
-
最值问题(如最大子序和、最小路径和等)
- 求解问题的最优解,如最短路径、最大值、最小值等。
- 典型问题:背包问题、股票买卖问题、最长公共子序列(LCS)等。
-
计数问题(如排列组合的计数)
- 计算问题的解的个数,如从起点到终点的路径数、组合数等。
-
分段问题(如剪绳子问题、分割问题等)
- 把问题分成若干段,分别求解后合并。
动态规划的经典例子:
1. 0/1 背包问题
问题描述:给定一个物品的重量和价值,求最大价值在不超过给定背包容量的条件下选择的物品集合。
def knapsack(weights, values, capacity):
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
for w in range(capacity, weights[i] - 1, -1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
时间复杂度:O(n * W),其中n是物品数量,W是背包容量。
2. 最长公共子序列(LCS)
问题描述:给定两个字符串,找出它们的最长公共子序列的长度。
def longestCommonSubsequence(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[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]
时间复杂度:O(m * n),其中m和n是两个字符串的长度。
3. 爬楼梯问题
问题描述:每次可以爬1阶或2阶,求到达第n阶的不同方法数。
def climbStairs(n):
if n == 1:
return 1
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]
时间复杂度:O(n),空间复杂度可以优化为O(1)。