python_13_暴力递归和动态规划

1 题目

在这里插入图片描述
通过测试

def minStickers1(stickers, target):
    n = len(stickers)
    maps = [[0] * 26 for _ in range(n)]   # [第一个贴纸][字母出现次数] 0下标对应a,1下标对应b   [2][25]
    for i in range(n):    # 第i个贴纸
        s = list(stickers[i])
        for j in s:    # 每个字符
            maps[i][ord(j) - ord('a')] += 1

    dp = {}
    dp[""] = 0  # 空字符的时候,返回0张贴纸
    return process1(dp, maps, target)

# dp 缓存,如果目标t已经算过了,直接返回dp中的值
# t 剩余的目标
# 0...N 每一个字符串所含字符的词频统计
def process1(dp, maps, rest):
    if rest in dp:      # 已经算过答案就直接拿
        return dp[rest]
    # 如果没有算过,以下是正式的递归调用过程
    ans = float("inf")   # 设置为无穷大,ans是搞定rest使用的最少的贴纸数量
    n = len(maps)   # 贴纸数量,n种贴纸
    tmap = [0] * 26      # tmap取代替rest
    target = list(rest)

    for c in target:             # target是目标字符串,tmap指定位置词频
        tmap[ord(c) - ord('a')] += 1  # 要组成字符的词频

    # map代表所有贴纸 ->去搞定 tmap代表剩余字符  这两全变成了词频统计的形式
    # 枚举当前第一张贴纸是谁
    for i in range(n):
        if maps[i][ord(target[0]) - ord('a')] == 0:                 # 这里有错误
            continue

        sb = []
        for j in range(26):
            if tmap[j] > 0:   # j这个字符是target需要的
                for k in range(max(0, tmap[j] - maps[i][j])):   # 剩余多少,在sb里添多少个作为剩余
                    sb.append(chr(ord('a') + j))

        s = ''.join(sb)
        tmp = process1(dp, maps, s)
        if tmp != -1:
            ans = min(ans, 1 + tmp)

    dp[rest] = -1 if ans == float("inf") else ans
    return dp[rest]

arr = ['aaaa', 'bbaa', 'ccddd']
s = "abcccccdddddbbbaaaaa"
print(minStickers1(arr, s))

2 关于递归一些问题

  • 什么暴力递归可以继续优化?
  1. 有重复调用同一个子问题的解,这种递归可以优化
  2. 如果每一个子问题都是不同的解,无法优化也不用优化
  • 暴力递归和动态规划的关系
  1. 某一个暴力递归,有解的重复调用,就可以把这个暴力递归优化成动态规划
  2. 任何动态规划问题,都一定对应着某一个有解的重复调用的暴力递归,但不是所有的暴力递归,都一定对应着动态规划
  • 如何找到某个问题的动态规划方式?
  1. 设计暴力递归:重要原则+4种常见模型
  2. 分析有没有重复解:套路解决
  3. 用记忆化搜索->用严格表结构实现动态规划:套路解决
  4. 看看能否继续优化:套路解决
  • 设计暴力递归过程的原则:
  1. 每一个可变参数的类型,一定不要比int类型更加复杂
  2. 1可以违反,让类型突破到一维线性结构,那必须是唯一可变参数
  3. 如果发现原则1被违反,但不违反原则2,只需要做到记忆化搜索即可
  4. 可变参数的个数,能少则少
  • 设计暴力递归过程的原则:
  1. 一定要b自己找到不违反原则情况下的暴力尝试
  2. 如果你找到的暴力尝试,不符合原则,马上舍弃,找新的
  3. 5%才有可能突破原则
  • 常见的4种尝试模型:
  1. 从左到右的尝试模型->从左向右考虑每个位置要还是不要->数字字符串转字母,背包问题
  2. 范围上的尝试模型->纸牌赢
  3. 多样本位置全对应的尝试模型
  4. 寻找业务限制的尝试模型
  • 暴力递归到动态规划的套路:
  1. 你已经有了一个不违反原则的暴力递归,而且的确存在解的重复调用
  2. 找到哪些参数的变化会影响返回值,对每一个列出变化范围
  3. 参数间的所有的组合数量,意味着表大小
  4. 记忆化搜索的方法就是缓存
  5. 规定好严格表的大小 ,分析位置依赖顺序,然后从基础填写到最终解
  6. 对于有枚举行为的决策过程,进一步优化
  • 动态规划进一步优化:
  1. 空间压缩
  2. 状态化简
  3. 四边形不等式

3 多样本位置全对应的尝试模型

题目:两个字符串的最长公共子序列问题
中间可以不连续
任何一个格子来源于左上角的格子
在这里插入图片描述
在这里插入图片描述
可能性2,有可能值来自左边
在这里插入图片描述
可能性3,值来自上方
在这里插入图片描述
可能性4,前面的值+1
在这里插入图片描述

# 一个样本做行,一个样本做列的模型
def lcse(str1,str2):
    dp = [[0] * len(str2) for _ in range(len(str1))]
    dp[0][0] = 1 if str1[0] == str2[0] else 0

    for i in range(1,len(str1)):
        # 从第一行开始赋值,如果左边是0,那么字母相等就赋值1,如果前面已经赋值过了,就写前面的值
        dp[i][0] = max(dp[i - 1][0],1 if str1[i] == str2[0] else 0)

    for j in range(1, len(str2)):
        # 从列开始填
        dp[0][j] = max(dp[0][j - 1], 1 if str1[0] == str2[j] else 0)

    for i in range(1,len(str1)):
        for j in range(1,len(str2)):
            # 可能性2和可能性3
            dp[i][j] = max(dp[i - 1][j],dp[i][j - 1])
            # 观察法,观察位置得出的结论
            if(str1[i] == str2[j]):
                dp[i][j] = max(dp[i][j],dp[i - 1][j - 1] + 1)

    return dp[len(str1) - 1][len(str2) - 1]

4 寻找业务限制的尝试模型

在这里插入图片描述

# a 洗一杯的时间 固定变量
# b 自己挥发干净的时间 固定变量
# drinks 每一个员工喝完的时间 固定变量
# drinks[0....index-1]都已经干净了,不用操心了
# drinks[index...]都想变干净,这是我要操心的
# washLine 表示洗的机器何时可用
# drinks[index...]变干净,最少的时间点返回
# 主函数调用 process(drinks,3,10,0,0)  3时间洗完,10挥发,从0开始洗,机器从0时可用
def process(drinks,a,b,index,washLine):      # 从左向右尝试模型
    if index == len(drinks) - 1:
        return min(
            max(washLine,drinks[index]) + a     # 即使机器空着,也得等员工喝完咖啡才能洗
            ,drinks[index] + b
        )   # 找花费最少时间
    # 剩不止一杯咖啡
    # wash是我当前的一杯咖啡杯,洗完的时间
    wash = max(washLine,drinks[index]) + a  # 洗,index一杯,结束的时间点
    # index+1...变干净的最早时间,后面的杯子也得洗完
    next1 = process(drinks,a,b,index + 1,wash)
    # index...
    p1 = max(wash,next1)   # 既要自己洗完,其他也要洗完

    # 可能性二,就决定挥发了
    dry = drinks[index] + b  # 挥发,index一杯,结束的时间点
    next2 = process(drinks,a,b,index + 1,washLine)    # 挥发过程,机器还空着,可以送去洗咖啡杯
    p2 = max(dry,next2)

    return min(p1,p2)

arr = [1,1,5,5,7,10,12,12,12,12,12,15]
a = 3
b = 10
print(process(arr,a,b,0,0))

改动态规划


def dp(drinks,a,b):
    if a >= b:  # 挥发时间比洗的时间短,不用洗了
        return drinks[len(drinks - 1)] + b
    # a < b
    limit = 0           # 咖啡机什么时间可用
    N = len(drinks)
    for i in range(len(drinks)):
        limit = max(limit,drinks[i] + a)

    dp = [[0] * (limit + 1) for _ in range(N)]
    # N - 1,所有的值
    for washLine in range(limit + 1):
        dp[N - 1][washLine] = min(
            max(washLine, drinks[N - 1]) + a  # 即使机器空着,也得等员工喝完咖啡才能洗
            , drinks[N - 1] + b
          # 找花费最少时间
        )

    for index in range(N - 2,-1,-1):
        for washLine in range(limit + 1):
            # dp[index][washLine] = ?
            p1 = float("inf")
            wash = max(washLine,drinks[index]) + a
            if wash <= limit:
                p1 = max(wash,dp[index + 1][wash])
            p2 = max(drinks[index] + b,dp[index + 1][washLine])
            dp[index][washLine] = min(p1,p2)


    return dp[0][0]

arr = [1,1,5,5,7,10,12,12,12,12,12,15]
a = 3
b = 10
print(dp(arr,a,b))

6 练习题目

象棋,马从(0,0)处走,走向(x,y)的位置一共有多少种方法?
在这里插入图片描述

def ways(x,y,k):   # (x,y)位置,k是马只能走几步
    return f(x,y,k)

def f(x,y,k):
    if k == 0:  # 如果只能走0步,走到(0,0)是一种方法,原地走是0种方法
        return 1 if x == 0 and y == 0 else 0
    if x < 0 or x > 9 or y < 0 or y > 8:   # 越界
        return 0
    # 有步数要走,x,y也是棋盘上的位置   下面递归见图,马可以走8个点
    return f(x + 2,y - 1,k - 1) + f(x + 2, y + 1, k - 1) + f(x + 1, y + 2, k - 1) + f(x - 1, y + 2, k - 1) + f(x - 2, y + 1, k - 1) + f(x - 2, y - 1, k - 1) + f(x - 1, y - 2, k - 1) + f(x + 1, y - 2, k - 1)

x = 2
y = 3
k = 3
print(ways(x,y,k))

改动态规划
三个可变参数递归,把所有返回值装到一张表,就是三维表
把所有可变参数组合都列全了,把缓存结构化了就是动态规划
在这里插入图片描述

def ways(x,y,k):   # (x,y)位置,k是马只能走几步
    dp = [[[0] * (k + 1) for _ in range(9)] for _ in range(10)]          # x范围0-9,给它10个空间,给k个空间是因为,k只会减小
    dp[0][0][0] = 1   # 其余位置都是0
    for level in range(1,k + 1):
        # level 层
        for i in range(10):  # x可能性
            for j in range(9):   # y的可能性
                dp[i][j][level] = getValue(dp,i + 2,j - 1,level - 1) + getValue(dp,i + 2, j + 1, level - 1) + getValue(dp,i + 1, j + 2, level - 1) + getValue(dp,i - 1, j + 2, level - 1) + getValue(dp,i - 2, j + 1, level - 1) + getValue(dp,i - 2, j - 1, level - 1) + getValue(dp,i - 1, j - 2, level - 1) + getValue(dp,i + 1, j - 2, level - 1)

    return dp[x][y][k]

def getValue(dp,x,y,k):
    if x < 0 or x > 9 or y < 0 or y > 8:   # 越界
        return 0
    return dp[x][y][k]

x = 2
y = 3
k = 3
print(ways(x,y,k))

7 斐波那契数列

从左往右尝试模型
改为矩阵乘法

def f(n):
    if n < 1:
        return 0
    if n == 1 or n == 2:
        return 1
    # [1,1]
    # [1,0]
    base = [[1,1],[1,0]]
    res = matrixPower(base,n - 2)    # 矩阵平方
    return res[0][0] + res[1][0]

def matrixPower(m,p):   # 求矩阵的p次方
    res = [[0] * len(m[0]) for _ in range(len(m))]
    for i in range(len(res)):
        res[i][i] = 1       # 单位矩阵
    tmp = m  # 矩阵1次方
    while p != 0:
        if (p & 1) != 0:   # 二进制向右移动
            res = muliMateix(res,tmp)
        tmp = muliMateix(tmp,tmp)
        p >>= 1
    return res

def muliMateix(m1,m2):   # 两个矩阵相乘
    res = [[0] * len(m2[0]) for _ in range(len(m1))]
    for i in range(len(m1)):
        for j in range(len(m2[0])):
            for k in range(len(m2)):
                res[i][j] += m1[i][k] * m2[k][j]

    return res

print(f(19))

7.1 奶牛问题

7.2 达标字符串

给定一个数N,想象只由0和1两种字符,组成的所有长度为N的字符串,如果某个字符串,任何0字符的左边都有1紧挨着,认为这个字符串达标,返回有多少达标字符串?

# 类斐波那契
def getnum1(n):
    if n < 1:
        return 0
    return process(1,n)  # 第0位置固定为1了

def process(i,n):
    if i == n - 1:
        return 2
    if i == n:
        return 1
    return process(i+1,n)+process(i+2,n)



def getnum2(n):
    if n < 1:
        return 0
    if n == 1:
        return 1
    pre = 1
    cur = 1
    tmp = 0
    for i in range(2,n + 1):
        tmp = cur
        cur += pre
        pre = tmp
    return cur


def getnum3(n):
    if n < 1:
        return 0
    if n == 1 or n == 2:
        return n
    base = [[1,1],[1,0]]
    res = matrixPower(base,n - 2)
    return 2 * res[0][0] + res[1][0]

def matrixPower(m,p):   # 求矩阵的p次方
    res = [[0] * len(m[0]) for _ in range(len(m))]
    for i in range(len(res)):
        res[i][i] = 1       # 单位矩阵
    tmp = m  # 矩阵1次方
    while p != 0:
        if (p & 1) != 0:   # 二进制向右移动
            res = muliMateix(res,tmp)
        tmp = muliMateix(tmp,tmp)
        p >>= 1
    return res

def muliMateix(m1,m2):   # 两个矩阵相乘
    res = [[0] * len(m2[0]) for _ in range(len(m1))]
    for i in range(len(m1)):
        for j in range(len(m2[0])):
            for k in range(len(m2)):
                res[i][j] += m1[i][k] * m2[k][j]

    return res

print(getnum1(5))
print(getnum2(5))
print(getnum3(5))

7.3 瓷砖排放问题

用斐波那契方法:严格没有条件转移表达式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值