本博客就上一期中讨论的蓝桥杯动态规划基础问题(包括:递推、记忆化搜索、最长公共子序列和最长上升子序列),给出了六个常见的案例问题。
每一个问题都给出了其求解方法的示例代码,以供低年级师弟师妹们学习和练习。如有不懂,欢迎在评论区提问。
前序知识:
(1)Python基础语法
(2)Day1:基础算法
(3)Day7:动态规划(基础)
def climb_stairs(n):
# dp[i]表示到达第i阶的方法数
dp = [0]*(n+1)
dp[0] = 1 # 初始状态:地面只有1种方法(不动)
dp[1] = 1 # 第一阶只有1种方法
for i in range(2, n+1):
# 状态转移:可以从i-1阶爬1步,或i-2阶爬2步
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(climb_stairs(5)) # 输出:8(5阶楼梯共有8种走法)
def coin_change(coins, amount):
# dp[i]表示凑出金额i需要的最小硬币数
dp = [float('inf')] * (amount+1)
dp[0] = 0 # 金额0不需要硬币
for i in range(1, amount+1):
for coin in coins:
if i >= coin:
# 状态转移:考虑使用当前硬币的情况
dp[i] = min(dp[i], dp[i-coin]+1)
return dp[amount] if dp[amount] != float('inf') else -1
print(coin_change([1,2,5], 11))
# 表示:共有硬币面额1、2和5,凑出11元钱。
# 输出:3(5+5+1)
def unique_paths(grid):
memo = {}
m, n = len(grid), len(grid[0])
def dfs(i, j):
# 遇到障碍物或越界返回0
if i >= m or j >= n or grid[i][j] == 1:
return 0
# 到达终点返回1
if i == m-1 and j == n-1:
return 1
# 查询缓存
if (i,j) in memo:
return memo[(i,j)]
# 递归计算并缓存结果
# 可以选择向下走(dfs(i+1, j))或向右走(dfs(i, j+1))
# 两者的结果加起来,得到从当前位置到终点的所有路径数
memo[(i,j)] = dfs(i+1,j) + dfs(i,j+1)
return memo[(i,j)]
return dfs(0,0)
# 测试:0表示可通行,1表示障碍物
grid = [
[0,0,0,0],
[0,1,0,0],
[0,0,0,0],
[0,0,0,0]
]
print(unique_paths(grid)) # 输出:8
def dna_similarity(dna1, dna2):
m, n = len(dna1), len(dna2) # m 和 n 分别表示 DNA 序列 dna1 和 dna2 的长度
# dp[i][j] 表示 dna1 的前 i 个字符与 dna2 的前 j 个字符的最长公共子序列的长度
# 这里多一行和多一列用于处理边界情况(空字符串的情况)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
# 匹配情况:如果 dna1[i-1] 等于 dna2[j-1],则说明当前两个字符相同
# 可以将公共子序列的长度加 1。即:dp[i][j] = dp[i-1][j-1] + 1
if dna1[i-1] == dna2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
# 不匹配情况:如果两个字符不同,则无法延长当前的公共子序列
# 所以下一位置的公共子序列长度为 dp[i-1][j] 或 dp[i][j-1] 的最大值
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
# 计算相似度比例
return dp[m][n] / max(m, n)
seq1 = "ATGCGAT"
seq2 = "ATCGAAT"
print(f"相似度:{dna_similarity(seq1, seq2):.4f}")
# 输出:相似度:0.8571
def max_flights(flights):
# 按起飞时间排序
flights.sort(key=lambda x: x[0])
n = len(flights)
dp = [1]*n
for i in range(n):
for j in range(i):
# 当前航班起飞时间 >= 前序航班降落时间
if flights[i][0] >= flights[j][1]:
dp[i] = max(dp[i], dp[j]+1)
return max(dp) if n > 0 else 0
# 每个航班格式:[起飞时间, 降落时间]
flights = [[1,3], [2,5], [4,7], [6,9]]
print(max_flights(flights)) # 输出:2(选择1-3,4-7)
def max_envelopes(envs):
# 按宽度升序,高度降序排列
envs.sort(key=lambda x: (x[0], -x[1]))
# 转换为寻找高度的LIS
heights = [e[1] for e in envs]
tails = []
for h in heights:
# 二分查找插入位置
left, right = 0, len(tails)
while left < right:
mid = (left+right) // 2
if tails[mid] < h:
left = mid + 1
else:
right = mid
# 更新tails数组
if left == len(tails):
tails.append(h)
else:
tails[left] = h
return len(tails)
envelopes = [[5,4],[6,4],[6,7],[2,3]] # 存放的是信封的宽和高
print(max_envelopes(envelopes)) # 输出:3([2,3]=>[5,4]=>[6,7])