五大基础算法 - 动态规划合集

164 阅读3分钟

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. 算法:不同路径

image.png

  • 核心方程: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]
  • 存在障碍的情况
      1. 障碍点,是无法抵达的点,是到达方式数为 0 的点
      1. 是无法从它这里走到别的点的点,即无法提供给别的点方式数的点
    • 核心代码:
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. 算法:最小路径和

image.png

  • 核心代码:
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. 算法:交错字符串

image.png

  • 核心代码:
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. 背包问题

image.png

  • 核心代码
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())