1. 背景
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。
20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
2. 动态规划与递归的关系
动态规划是自底向上,递归树是自顶向下
为什么动态规划一般都脱离了递归,而是由循环迭代完成计算。
- 自底向上 -> 动态规划(dp)
-
- 例如斐波那契数列 求f(20),依次从f(1),f(2) 每次记录下各个计数,减少计算次数。
- 自顶向下 -> 递归
-
- 例如斐波那契数列 求f(20) 需要先求到f(20) = f(19)+f(18) 从顶向下 依次返回答案。
- 例如:斐波那契数列 核心代码
def fib(n):
if n <= 2: return 1
return fib(n - 1) + fib(n - 2)
def dp_fib(n):
dp = [0] * (n + 1)
dp[1] = dp[2] = 1
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
def gd_fib(n):
"""滚动数组 确定只与哪些有关系 则通过赋值覆盖并持续推导的方式进行累加-节省空间"""
gd = [0, 0, 0]
gd[1] = gd[2] = 1
for i in range(n):
gd[0] = gd[1]
gd[1] = gd[2]
gd[2] = gd[0] + gd[1]
return gd[0]
3. 状态转移方程
状态转移方程的重要性,它是解决问题的核心。- 很容易发现,其实状态转移方程直接代表着暴力解法。
- 千万不要看不起暴力破解,动态规划问题最困难的就是写出状态转移方程
4. 算法:不同路径
- 核心方程:
f(i,j) = f(i-1, j) + f(i, j-1) - 核心代码
class Solution(object):
def uniquePaths(self, m: int, n: int) -> int:
if m == n == 1:
return 1
dp = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
if i == 0 or j == 0:
# 处理边界值
dp[i][j] = 1
else:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
- 存在障碍的情况
-
-
- 障碍点,是无法抵达的点,是到达方式数为 0 的点
-
-
-
- 是无法从它这里走到别的点的点,即无法提供给别的点方式数的点
-
-
- 核心代码:
def fun(lst):
row = len(lst)
col = len(lst[0])
dp = [[0] * col for _ in range(row)]
for i in range(row):
if lst[i][0] == 1: break
dp[i][0] = 1
for i in range(col):
if lst[0][i] == 1: break
dp[0][i] = 1
for i in range(row):
for j in range(col):
if lst[i][j] != 0: continue
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[row - 1][col - 1]
if __name__ == '__main__':
print(fun([[0, 0, 0], [0, 1, 0], [0, 0, 0]]))
5. 算法:最小路径和
- 核心代码:
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
for i in range(m):
for j in range(n):
if i == j == 0:
# 边界值表示自身 不需要进行替换
continue
elif i == 0:
# 边界值表示处于第一行,不能够进行 i-1 操作
grid[i][j] = grid[i][j - 1] + grid[i][j]
# 边界值表示处于第一列,不能够进行 j-1 操作
elif j == 0:
grid[i][j] = grid[i - 1][j] + grid[i][j]
else:
grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]
return grid[m - 1][n - 1] # 返回右下角的值 就是替换后的最小的值
6. 算法:交错字符串
- 核心代码:
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
l1 = len(s1)
l2 = len(s2)
l3 = len(s3)
if l1 + l2 != l3: return False
dp = [[False] * (l2 + 1) for _ in range(l1 + 1)]
for i in range(l1 + 1):
for j in range(l2 + 1):
if i == j == 0:
dp[0][0] = True
elif j == 0:
dp[i][0] = dp[i - 1][0] and s1[i - 1] == s3[i - 1]
elif i == 0:
dp[0][j] = dp[0][j - 1] and s2[j - 1] == s3[j - 1]
else:
dp[i][j] = (dp[i][j - 1] and s2[j - 1] == s3[i + j - 1]) or (
dp[i - 1][j] and s1[i - 1] == s3[i + j - 1])
return dp[-1][-1]
7. 背包问题
- 核心代码
def inp():
v, n = list(map(int, input().split()))
lst = []
for _ in range(n):
lst.append(list(map(int, input().split())))
dp = [[0] * (v + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
vi = lst[i - 1][0]
wi = lst[i - 1][1]
for j in range(1, v + 1):
if vi > j:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - vi] + wi)
return dp[-1][-1]
if __name__ == '__main__':
print(inp())