1. 动态规划框架

  • 核心:穷举求最值
  • 动态规划特点:
    • 重叠子问题
    • 状态转移方程(最关键)
    • 最优子结构
  • 解题套路:
    • 明确状态
    • 明确选择
    • 明确dp函数/数组的定义
    • 明确base case
  • DP解法代码框架:
# 1.创建base case
dp[0][0][...] = base
# 2.初始化base case
# 3.穷举状态
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            # 4.状态转移
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

2. 斐波那契数

class Solution:
    # 最优解法:最终优化空间复杂度(% 1000000007防越界)
    def fib(n):
        i,j = 0,1
        for _ in range(n):
            i, j = j, i + j
        return i % 1000000007
    # 基础递归
    def fib(n):
        if n == 0 or n == 1: return n
        return fib(n-1) + fib(n-2)
    # 递归优化
    def fib(n):
        # 递归剪枝:将计算过的节点存起来,空间换时间
        _dict = [0 for i in range(n+1)]
        # 递归方法
        def rec(n):
            if n == 0 or n == 1: return n
            if _dict[n] != 0: return _dict[n]
            _dict[n] = rec(n-1) + rec(n-2)
            return _dict[n]
        return rec(n)
    # 动态规划(自顶向下)
    def fib(n):
        if n == 0: return 0
        dp = [0 for i in range(n+1)]
        dp[0], dp[1] = 0, 1
        for i in range(2,n+1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]
    # 最优解法:优化空间复杂度
    def fib(n):
        i,j = 0,1
        for _ in range(n):
            i, j = j, i + j
        return i
# UT
print(Solution().fib(7))

3. 青蛙台阶

# 一次可以跳1阶/2阶
class Solution:
    def numWays(self, n: int) -> int:
        a, b = 1, 1
        for _ in range(n):
            a, b = b, a + b
        return a % 1000000007

4. 最长公共子串

def longestCommonString(self, A: str, B: str) -> int:
    # 因为需要遍历字符串并构建存储矩阵,所以需要求两个字符串的长度
    m, n = len(A), len(B)
    # 初始化为0的存储矩阵,dp[i][j]用于存储该‘单元’之前的子串长度
    dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    # 初始化最终结果,长度为整型,默认为0
    ans = 0
    # 因为需要判断子问题,所以i,j以1而不是0开始,结束也是对应的m+1和n+1
    # (实质遍历的是存储单元,与字符串遍历相差为1)
    # 对于字符串,m+1,n+1指针是越界的,但是对于存储,是不越界的。
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            # 通过子问题判断,来决定当前的d[i][j]的值
            # d[i][j]存储的就是之间累加的子串长度
            if A[i - 1] == B[j - 1]:
                # 如果两个字符串的当前字符相等,那么dp[i][j]的值就是之前d[i-1][j-1]存储的长度+1
                dp[i][j] = dp[i - 1][j - 1] + 1
                # 新得到的最长序列要和上一次得到的最长序列结果ans取最大成为新的ans
                ans = max(ans, dp[i][j])
            # 此处不需要else:与公共子序列相比不需要考虑字符不相同的情况,不相同直接从0计数
    return ans
# UT
a, b = 'abcdef','abf'
print(longestCommonString(a, b))

- 与子序列相比,子串只是不去考虑双重循环中,条件语句中的else
- 因为默认矩阵为0,即如果A[i - 1] != B[j - 1]即出现不连续,那么从0开始重新计数
- 而子序列却需要从附近的存储单元来延续这个数值

5. 最长公共子序列

def longestCommonSubsequence(a: str, b: str) -> int:
    m, n = len(a), len(b)
    dp = [[0 for _ in range(n + 1)] for __ in range(m + 1)]
    ans = 0
    for i in range(1, m + 1):  # 规划表 外层
        for j in range(1, n + 1):  # 规划表 内层
            if a[i - 1] == b[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
                ans = max(ans, dp[i][j])
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return ans
# UT
a, b = 'abcdef','abf'
print(longestCommonSubsequence(a, b))

6. 零钱兑换

from typing import List
import sys
# 暴力递归
def coinChange(coins:List[int],amount:int):
    # base case
    if amount == 0 : return 0
    if amount < 0: return -1

    res = sys.maxsize
    for i in coins:
        tmp = coinChange(coins,amount-i)
        if tmp == -1: continue
        res = min(res,tmp+1)
    return res == sys.maxsize and -1 or res

# 递归优化(自顶向下递归)
def coinChange(coins:List[int],amount:int):
    _dict = [ -1000 for i in range(amount+1)]
    def dp(coins:List[int],amount:int):
        if amount == 0: return 0
        if amount < 0: return -1
        # 优化关键点
        if _dict[amount] != -1000:
            return _dict[amount]
        # 计算子问题
        res = sys.maxsize
        for i in coins:
            tmp = dp(coins,amount-i)
            if tmp == -1: continue
            res = min(res,tmp+1)

        _dict[amount] = res == sys.maxsize and -1 or res
        return _dict[amount]

    return dp(coins,amount)
# 动态规划:自底向上迭代解法
def coinChange(coins:List[int],amount:int):
    dp = [amount+1 for i in range(amount+1)]
    print(dp)
    dp[0] = 0
    for i in range(amount+1):
        for coin in coins:
            if i - coin < 0: continue
            dp[i] = min(dp[i],1+dp[i-coin])
    return dp[amount] == amount + 1 and -1 or dp[amount]

print(coinChange([1,2,5],11))

7. 编辑距离

class Solution:
    # 方法一:递归
    def edit_distance_rec(self, word1: str, word2: str) -> int:
        l1,l2 = len(word1),len(word2)
        if l1 == 0 or l2 == 0 :
            return max(l1,l2)
        if word1[l1-1] == word2[l2-1]:
            return self.edit_distance_rec(word1[0:-1], word2[0:-1])
        else:
            return min([
                self.edit_distance_rec(word1[:-1], word2),
                self.edit_distance_rec(word1, word2[:-1]),
                self.edit_distance_rec(word1[:-1], word2[:-1])
                ]) + 1
    # 方法二:动态规划
    def edit_distance_dp(self, word1: str, word2: str) -> int:
        n = len(word1)
        m = len(word2)
        # 有一个字符串为空串
        if n * m == 0:
            return n + m
        # DP 数组
        D = [ [0] * (m + 1) for _ in range(n + 1)]
        # 边界状态初始化:base case
        for i in range(n + 1):
            D[i][0] = i
        for j in range(m + 1):
            D[0][j] = j
        # 计算所有 DP 值:穷举状态
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                left = D[i - 1][j] + 1
                down = D[i][j - 1] + 1
                left_down = D[i - 1][j - 1]
                # 状态转移
                if word1[i - 1] != word2[j - 1]:
                    left_down += 1
                D[i][j] = min(left, down, left_down)

        return D[n][m]
#UT
w1,w2 = "banbana","baa"
print(Solution().edit_distance_rec(w1,w2))
print(Solution().edit_distance_dp(w1,w2))

8. 机器人(0,0)到(x,y)走法

# 0,0到x,y所有走法(下、右)
# 1.暴力递归
def recursion(x, y):
    if x == 1 or y == 1:
        return 1
    x, y = abs(x), abs(y)
    return recursion(x-1, y) + recursion(x, y-1)

# 2.dp动态规划
def dp_func(x, y):
    """
    dp[i][0] = 1 for i = 0,...,x-1
    dp[0][j] = 1 for j = 0,...,y-1
    dp[x][y] = dp[x-1][y] + dp[x][y-1]
    """
    if x == 0 and y == 0:
        return 0
    if x == 0 or y == 0:
        return 1
    x, y = abs(x), abs(y)
    dp = [[0 for _ in range(y)] for _ in range(x)]
    for i in range(x):
        dp[i][0] = 1
    for i in range(y):
        dp[0][i] = 1
    for i in range(1, x):
        for j in range(1, y):
            dp[i][j] = dp[i][j-1] + dp[i-1][j]
    return dp[x-1][y-1]

def dp_func2(x,y):
    dp = [1 for _ in range(x)]
    for i in range(1,x):
        for j in range(1,y):
            dp[j] += dp[j-1]
    return dp[y-1]

print(recursion(4,4))
print(dp_func(4,4))
print(dp_func2(4,4))
Copyright © 2021 zbmain.  all right reserved,powered by Gitbook本页修订时间: 2021-04-22

results matching ""

    No results matching ""