目录
1.0/1 背包问题:
2 最长递增子序列(LIS):
3 最长公共子序列(LCS):
4 最小编辑距离(Edit Distance):
5 最短路径问题(单源最短路径):
6 区间调度问题:
7 最长回文子序列:
8 切割钢条问题:
动态规划是一种通过将原问题分解为相互重叠的子问题,并通过已解决的子问题的解来求解原问题的方法。动态规划算法通常用于优化问题,其中需要做出一系列决策,每个决策都会影响到后续的决策。以下是一些常见的动态规划算法:
目标是选择一组物品放入背包,使得总重量不超过背包容量且总价值最大。
题目:给定n个物品,每个物品有自己的重量和价值,要求从中选择一些物品放入容量为C的背包中,使得放入的物品总重量不超过C且总价值最大。
思路:定义一个二维数组dp[i][j]表示在前i个物品中选择总重量不超过j的情况下的最大总价值。
状态转移方程为:
,
其中w[i]为第i个物品的重量,v[i]为其价值。
示例代码:
def knapsack(weight, value, C):
n = len(weight)
dp = [[0] * (C+1) for _ in range(n+1)]
for i in range(1, n+1):
for j in range(1, C+1):
if j >= weight[i-1]:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i-1]] + value[i-1])
else:
dp[i][j] = dp[i-1][j]
return dp[n][C]
# 测试
weight = [2, 3, 4, 5]
value = [3, 4, 5, 6]
C = 8
result = knapsack(weight, value, C)
print(result) # 输出:10
题目:给定一个序列,找出最长的递增子序列的长度。
思路:定义一个一维数组dp,其中dp[i]表示以第i个元素结尾的最长递增子序列的长度。
状态转移方程为:
,
其中0 <= j < i且nums[j] < nums[i]。
示例代码:
def lengthOfLIS(nums):
n = len(nums)
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j]+1)
return max(dp)
# 测试
nums = [10, 9, 2, 5, 3, 7, 101, 18]
result = lengthOfLIS(nums)
print(result) # 输出:4
题目:给定两个序列,找出它们的最长公共子序列的长度。
思路:定义一个二维数组dp,其中dp[i][j]表示序列s1的前i个字符和序列s2的前j个字符的最长公共子序列的长度。
状态转移方程为:
,if ;
,if 。
示例代码:
def longestCommonSubsequence(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[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]
# 测试
s1 = "abcde"
s2 = "ace"
result = longestCommonSubsequence(s1, s2)
print(result) # 输出:3
题目:给定两个字符串s1和s2,通过一系列插入、删除、替换操作,将s1转换为s2,求最小的编辑距离。
思路:定义一个二维数组dp,其中dp[i][j]表示将s1的前i个字符转换为s2的前j个字符所需的最小编辑距离。
状态转移方程为:
,如果;
, ,
如果。
示例代码:
def minEditDistance(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(1, m+1):
dp[i][0] = i
for j in range(1, n+1):
dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[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]
# 测试
s1 = "intention"
s2 = "execution"
result = minEditDistance(s1, s2)
print(result) # 输出:5
题目:给定一个加权有向图和起点S,求从S到所有其他节点的最短路径。
思路:使用Dijkstra算法或Bellman-Ford算法来解决。这里以Dijkstra算法为例。
示例代码:
import heapq
def dijkstra(graph, S):
n = len(graph)
distance = [float('inf')] * n
distance[S] = 0
pq = [(0, S)] # 使用优先队列按距离从小到大选择节点
while pq:
d, node = heapq.heappop(pq)
if d > distance[node]:
continue
for neighbor, weight in graph[node]:
new_distance = distance[node] + weight
if new_distance < distance[neighbor]:
distance[neighbor] = new_distance
heapq.heappush(pq, (new_distance, neighbor))
return distance
# 测试
graph = [[(1, 4), (2, 2)], # 节点0的邻接节点和边权重
[(3, 3)], # 节点1的邻接节点和边权重
[(1, 1), (3, 5)], # 节点2的邻接节点和边权重
[(4, 2)], # 节点3的邻接节点和边权重
[]] # 节点4的邻接节点和边权重
S = 0
result = dijkstra(graph, S)
print(result) # 输出:[0, 4, 2, 6, inf]
题目:给定一个包含n个区间的集合,找到最大的不重叠子集,即这个子集中的任意两个区间都没有重叠部分。
思路:对区间按照结束时间进行排序,选择结束时间最早的区间作为第一个区间,然后依次遍历剩余区间,如果当前区间的开始时间晚于前一个区间的结束时间,则将其加入不重叠子集。
示例代码:
def intervalSchedule(intervals):
n = len(intervals)
if n == 0:
return 0
intervals.sort(key=lambda x: x[1]) # 按结束时间进行排序
count = 1
end = intervals[0][1]
for i in range(1, n):
if intervals[i][0] >= end:
count += 1
end = intervals[i][1]
return count
# 测试
intervals = [(1, 3), (2, 4), (3, 6), (5, 7), (6, 8)]
result = intervalSchedule(intervals)
print(result) # 输出:3
题目:给定一个字符串,找出其中最长的回文子序列的长度。
思路:定义一个二维数组dp,其中dp[i][j]表示字符串从索引i到索引j的子串中的最长回文子序列的长度。
状态转移方程为:
,如果;
,如果。
示例代码:
def longestPalindromeSubseq(s):
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n-1, -1, -1):
dp[i][i] = 1
for j in range(i+1, n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return dp[0][n-1]
# 测试
s = "bbbab"
result = longestPalindromeSubseq(s)
print(result) # 输出:4
题目:给定一根长度为n的钢条和一个价格表,求切割钢条的方式,使得销售总收益最大。
思路:定义一个一维数组dp,其中dp[i]表示长度为i的钢条的最大收益。
状态转移方程为:
,
其中0 <= j <= i,price[j]表示长度为j的钢条的价格。
示例代码:
def cutRod(length, price):
n = len(price)
dp = [0] * (length+1)
for i in range(1, length+1):
for j in range(1, min(i+1, n)):
dp[i] = max(dp[i], dp[i-j] + price[j])
return dp[length]
# 测试
length = 8
price = [0, 1, 5, 8, 9, 10, 17, 17, 20]
result = cutRod(length, price)
print(result) # 输出:22
最长递增子序列(Longest Increasing Subsequence,LIS): 寻找给定序列中的最长递增子序列的长度。
最长公共子序列(Longest Common Subsequence,LCS): 给定两个序列,找到它们的最长公共子序列的长度。
最小编辑距离(Edit Distance): 通过一系列编辑操作(插入、删除、替换)将一个字符串转换为另一个字符串,求最小的编辑距离。
最短路径问题: 例如,单源最短路径(Dijkstra算法、Bellman-Ford算法)、全源最短路径(Floyd-Warshall算法)。
最大子数组和问题(Maximum Subarray Sum): 寻找给定数组中和最大的连续子数组。
背包问题的变种: 包括多重背包、完全背包、分数背包等。
区间调度问题: 给定一组区间,找到最大的不重叠子集。
最长回文子序列: 寻找给定字符串的最长回文子序列的长度。
切割钢条问题:给定一根长度为n的钢条和一个价格表,求切割钢条的方式,使得销售总收益最大。
这只是动态规划应用的一小部分,实际上,动态规划可以用于解决各种优化问题。每个动态规划问题都有其独特的状态定义、状态转移方程和初始条件。通常,动态规划问题可以分为自顶向下(递归加记忆化搜索)和自底向上(迭代)两种解法。