【算法-面试】动态规划之二维dp

# coding = "utf-8"

'''

'''


def isMatch(s, p):
    '''
    给你⼀个字符串 s 和⼀个字符规律 p,请你来实现⼀个⽀持 '.' 和 '*' 的正则表达式匹配,'.' 匹配任意单个字符 '*' 匹配零个或多个前⾯的那⼀个元素。
    算法返回 p 是否可以匹配整个字符串 s。
    leetcode: 10. 正则表达式匹配
    input:s = "aa" p = "a"
    output: false
    思路:
        1.
        2.
        3.
    '''
    m, n = len(s), len(p)

    class DP:
        def __init__(self):
            self.memo = dict()

        def run(self, s, i, p, j):
            # 1. base case
            # 模式串匹配结束
            if j == n:
                return i == m
            # 字符串匹配结束
            if i == m:
                if (n - j) % 2 == 1:  # j是剩余模式串的首个位置,如"ab*c*",则j=1
                    return False
                while j + 1 < n:
                    # j和j+1是字符与"*"成对出现的
                    if p[j + 1] != "*":
                        return False
                    j += 2
                return True
            key = str(i) + ',' + str(j)
            if key in self.memo:
                return self.memo[key]
            # 2. 是否匹配
            if s[i] == p[j] or p[j] == '.':
                # 2.1 匹配通配符.
                if j < n - 1 and p[j + 1] == '*':
                    # 匹配通配符* 0次或多次
                    # 如"ca" ".d*a" 匹配0次,调用递归,模式串往后移动2个位子
                    # 如"caa" ".a*" 匹配多次,调用递归,文本串往后移动1个位子,而模式串不用动
                    res = self.run(s, i, p, j + 2) or self.run(s, i + 1, p, j)
                else:
                    # 不匹配通配符* 比较下一个,文本串和模式串都往后移动1个位子
                    res = self.run(s, i + 1, p, j + 1)
            else:
                # 2.2 不匹配通配符.
                if j < n - 1 and p[j + 1] == '*':
                    # 匹配通配符* 则匹配0次 如"a" "b*a"
                    # 调用递归,模式串往后移动2个位子
                    res = self.run(s, i, p, j + 2)
                else:
                    res = False
            self.memo[key] = res
            return res

    dp = DP()
    r = dp.run(s, 0, p, 0)
    print(dp.memo)
    print(r)
    return r


def minPathSum(grid):
    '''
    给定⼀个包含⾮负整数的 m x n ⽹格 grid,请找出⼀条从左上⻆到右下⻆的路径,使得路径上的数字总和
    为最⼩(每次只能向下或者向右移动⼀步)。
    leetcode:64. 最⼩路径和
    input:grid = [
                    [1,3,1],
                    [1,5,1],
                    [4,2,1]
                 ]
    output:7
    思路:
        1.
        2.
        3.
    '''
    m, n = len(grid), len(grid[0])
    import math

    # 自顶向下
    class DP:
        def __init__(self):
            self.memo = [[-1 for _ in range(m)] for _ in range(n)]

        def run(self, grid, i, j):
            # base case
            # 1. i j 到第0格
            if i == 0 and j == 0:
                return grid[0][0]
            # 索引出界 用数学的最大值来替代 则递归的上一层一定不会选择该值
            if i < 0 or j < 0:
                return math.inf

            # 记忆化搜索
            if self.memo[i][j] != -1:
                return self.memo[i][j]

            # 2. 本格的值+上格或左格的最小值
            self.memo[i][j] = min(self.run(grid, i - 1, j), self.run(grid, i, j - 1)) + grid[i][j]

            return self.memo[i][j]

    dp = DP()
    res = dp.run(grid, m - 1, n - 1)
    print(res, dp.memo)
    return res


def minDistance(s1, s2):
    '''
    给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使⽤的最少操作数。
    你可以对⼀个单词进⾏如下三种操作:
    1、插⼊⼀个字符
    2、删除⼀个字符
    3、替换⼀个字符
    leetcode:72. 编辑距离
    input:word1 = "horse", word2 = "ros"
    output: 3
    input:word1 = "intention", word2 = "execution"
    output: 5
    思路:
        1.
        2.
        3.
    '''
    m, n = len(s1), len(s2)

    # 自底向上
    def b2u(s1, s2):
        m, n = len(s1), len(s2)
        dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
        for i in range(m + 1):
            dp[i][0] = i
        for j in range(n + 1):
            dp[0][j] = j
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if s1[i - 1] == s2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                else:
                    dp[i][j] = min(
                        dp[i - 1][j] + 1,
                        dp[i][j - 1] + 1,
                        dp[i - 1][j - 1] + 1
                    )
        return dp[m][n]

    class DP:
        def __init__(self, s1, s2):
            self.s1 = s1
            self.s2 = s2
            self.memo = {}

        def run(self, i, j):
            # base case i或j走完,直接返回 另一个 字符串剩下的长度
            if i == -1:
                return j + 1  # i走完了,返回s2剩下的长度
            if j == -1:
                return i + 1  # j走完了,返回s1剩下的长度
            key = str(i) + ',' + str(j)
            if key in self.memo:
                return self.memo[key]
            if self.s1[i] == self.s2[j]:
                self.memo[key] = self.run(i - 1, j - 1)  # 相等
            else:
                self.memo[key] = min(
                    self.run(i - 1, j) + 1,  # 删除
                    self.run(i, j - 1) + 1,  # 插入
                    self.run(i - 1, j - 1) + 1  # 替换
                )
            return self.memo[key]

    dp = DP(s1, s2)
    res1 = dp.run(m - 1, n - 1)
    res2 = b2u(s1, s2)
    print(res1, res2)
    return res1


def maxProfitWithK1(prices):
    '''
    给定⼀个数组 prices,它的第 i 个元素 prices[i] 表示⼀⽀给定股票第 i 天的价格。
    你只能选择某⼀天买⼊这只股票,并选择在未来的某⼀个不同的⽇⼦卖出该股票。设计⼀个算法来计算你所能获取的最⼤利润。
    返回你可以从这笔交易中获取的最⼤利润。如果你不能获取任何利润,返回 0。
    leetcode:121. 买卖股票的最佳时机
    input:[7,1,5,3,6,4]
    output:5
    思路:
        1. 定义dp table dp[i][k][s], 第i天最多交易k次手中持有状态为s
        2. buy的前提是k>0,buy之后才可以sell;持有状态s,s=1表示持有,s=0表示未持有
        3. 最终要推算出dp[n-1][K][0]的值,说明此时的股票已经全部卖出
        4.
    '''
    import math

    def universe_dp(prices):
        days, deals, status = len(prices), 1, 2
        dp = [[[0.0 for _ in range(status)] for _ in range(deals + 1)] for _ in range(days)]

        for i in range(days):
            dp[i][0][0] = 0  # 没有交易的时候,金额为0
            dp[i][0][1] = -math.inf  # 没有交易的时候,手上持有股票,不合理,用负无穷表示

        # 通用解法
        for i in range(days):
            for k in range(deals, 0, -1):
                if i - 1 == -1:
                    dp[i][k][0] = 0
                    dp[i][k][1] = - prices[i]
                    continue
                dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
                dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])
        res = dp[days - 1][deals][0]
        print(res)
        return int(res)

    def dp_without_k(prices):
        days, status = len(prices), 2
        dp = [[0.0 for _ in range(status)] for _ in range(days)]
        for i in range(days):
            dp[i][0] = 0
            dp[i][1] = -math.inf

        for i in range(days):
            if i - 1 == -1:
                dp[i][0] = 0
                dp[i][1] = -prices[i]
                continue

            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            # dp[i][1] = max(dp[i - 1][1], dp[i-1][0] - prices[i]) # k = 0 的 base case,所以 dp[i-1][0][0] = 0。
            dp[i][1] = max(dp[i - 1][1], - prices[i])
        res = dp[days - 1][0]
        print(res)
        return res

    res = universe_dp(prices)
    dp_without_k(prices)
    return res


def maxProfitWithKN(prices):
    '''
    给定⼀个数组 prices,其中 prices[i] 是⼀⽀给定股票第 i 天的价格。
    设计⼀个算法来计算你所能获取的最⼤利润。你可以尽可能地完成更多的交易(多次买卖⼀⽀股票)。
    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    leetcode:122. 买卖股票的最佳时机II
    input:[7,1,5,3,6,4]
    output:7
    思路:
        1.
        2.
        3.
    '''
    import math

    def universe_dp(prices):
        days, status = len(prices), 2
        dp = [[0.0 for _ in range(status)] for _ in range(days)]

        for i in range(days):
            dp[i][0] = 0
            dp[i][1] = - math.inf

        for i in range(days):
            if i - 1 == -1:
                dp[i][0] = 0
                dp[i][1] = -prices[i]
                continue

            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])

        res = dp[days - 1][0]
        print(res)
        return res

    return universe_dp(prices)


def maxProfitWithK2(prices):
    '''
    给定⼀个数组,它的第 i 个元素是⼀⽀给定的股票在第 i 天的价格。
    设计⼀个算法来计算你所能获取的最⼤利润。你最多可以完成 两笔 交易。
    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    leetcode:123. 买卖股票的最佳时机 III
    input:prices = [3,3,5,0,0,3,1,4]
    output:6
    思路:
        1. 最多交易两次
        2.
        3.
    '''
    days, deals, status = len(prices), 2, 2
    dp = [[[0 for _ in range(status)] for _ in range(deals + 1)] for _ in range(days)]
    for i in range(days):
        for k in range(deals, 0, -1):
            if i - 1 == -1:
                dp[i][k][0] = 0
                dp[i][k][1] = -prices[i]
                continue
            dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
            dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])

    res = dp[days - 1][deals][0]
    print(res)
    return res


def maxProfitWithK(prices, k):
    '''
    给定⼀个整数数组 prices,它的第 i 个元素 prices[i] 是⼀⽀给定的股票在第 i 天的价格。
    设计⼀个算法来计算你所能获取的最⼤利润。你最多可以完成 k 笔交易。
    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    leetcode: 188. 买卖股票的最佳时机 IV
    input:
    output:
    思路:
        1. 限制交易次数为k
        2.
        3.
    '''
    import math
    days, deals, status = len(prices), k, 2

    if deals > days / 2:
        res = maxProfitWithKN(prices)
        return res
    dp = [[[0.0 for _ in range(status)] for _ in range(deals + 1)] for _ in range(days)]
    for i in range(days):
        dp[i][0][0] = 0
        dp[i][0][1] = - math.inf

    for i in range(days):
        for k in range(deals, 0, -1):
            if i - 1 == -1:
                dp[i][k][0] = 0
                dp[i][k][1] = -prices[i]
                continue
            dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
            dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])

    res = dp[days - 1][deals][0]
    print(res)
    return res


def maxProfitWithFrozen(prices):
    '''
    给定⼀个整数数组,其中第 i 个元素代表了第 i 天的股票价格。
    设计⼀个算法计算出最⼤利润。在满⾜以下约束条件下,你可以尽可能地完成更多的交易(多次买卖⼀⽀股
    票):
    1、你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    2、卖出股票后,你⽆法在第⼆天买⼊股票 (即冷冻期为 1 天)。
    leetcode: 309. 最佳买卖股票时机含冷冻期
    input:[1,2,3,0,2]
    output:3
    思路:
        1. 多次交易,k无限
        2.
        3.
    '''
    import math
    days, status = len(prices), 2
    dp = [[0.0 for _ in range(status)] for _ in range(days)]

    for i in range(days):
        dp[i][0] = 0
        dp[i][1] = -math.inf

    for i in range(days):
        if i - 1 == -1:
            dp[i][0] = 0
            dp[i][1] = -prices[i]
            continue
        if i - 2 == -1:
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = max(dp[i - 1][1], - prices[i])
            continue
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
        dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i])
    res = dp[days - 1][0]
    print(res)
    return res


def maxProfitWithFee(prices, fee):
    '''
    给定⼀个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格;整数 fee 代表了交易股票的⼿续费⽤。
    你可以⽆限次地完成交易,但是你每笔交易都需要付⼿续费。如果你已经购买了⼀个股票,在卖出它之前你
    就不能再继续购买股票了。
    请你计算获得利润的最⼤值。
    注意:这⾥的⼀笔交易指买⼊持有并卖出股票的整个过程,每笔交易你只需要为⽀付⼀次⼿续费。
    leetcode: 714. 买卖股票的最佳时机含⼿续费
    input:[1, 3, 2, 8, 4, 9]
    output:8
    思路:
        1. 包含手续费 无限次交易
        2.
        3.
    '''
    import math
    days, status = len(prices), 2
    dp = [[0 for _ in range(status)] for _ in range(days)]
    for i in range(days):
        dp[i][0] = 0
        dp[i][1] = -math.inf
    for i in range(days):
        if i - 1 == -1:
            dp[i][0] = 0
            dp[i][1] = -prices[i] - fee
            continue
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee)

    res = dp[days - 1][0]
    print(res)
    return res


def calculateMinimumHP(grid):
    '''
    地下城是由 M x N 个房间组成的⼆维⽹格,骑⼠(K)最初被安置在左上⻆的房间⾥,他必须拯救关在地下城的右下⻆公主(P)。
    骑⼠的初始⽣命值为⼀个正整数,如果他的⽣命值在某⼀时刻降⾄ 0 或以下,他会⽴即死亡。
    每个矩阵元素代表地下城房间,如果房间的值为负整数,则表示骑⼠将遇到怪物损失⽣命值;如果房间的值为为0则说明什么都不会发⽣,如果房间的值为正整数,则表示骑⼠将增加⽣命值。
    为了尽快到达公主,骑⼠决定每次只向右或向下移动⼀步,编写⼀个函数来计算确保骑⼠能够拯救到公主所需的最低初始⽣命值。
    例如,考虑到如下布局的地下城,如果骑⼠遵循最佳路径 右 -> 右 -> 下 -> 下,则骑⼠的初始⽣命值⾄少为7。
    -2(K)  -3    3
    -5     -10   1
    10     30   -5(P)
    leetcode:174. 地下城游戏
    input:
    output:
    思路:
        1. 目的:为了得到最小的初始生命值
        2.
        3.
    '''
    import math
    m, n = len(grid), len(grid[0])

    class DP:
        def __init__(self, m, n):
            self.m = m
            self.n = n
            # 初始化为-1,更新过后的memo不会为负数
            self.memo = [[-1 for _ in range(n)] for _ in range(m)]

        def run(self, _grid, i, j):
            # base case 最后一格遇到血瓶,则只需要最小生命值即可
            if i == self.m - 1 and j == self.n - 1:
                if _grid[i][j] >= 0:
                    # 遇到血瓶或者无风险 只需要最小生命值即可
                    return 1
                else:
                    # 遇到怪物
                    return -_grid[i][j] + 1
            # 遇到边界
            if i == self.m or j == self.n:
                return math.inf

            if self.memo[i][j] != -1:
                return self.memo[i][j]

            res = min(
                self.run(_grid, i, j + 1),  # 向下走
                self.run(_grid, i + 1, j)  # 向右走
            ) - _grid[i][j]  # 下一步所需的最小升值-当前值=上一步所需要的生命值
            # 其中怪物房的值是负值,因此得到的肯定是正值

            # 生命值至少为 1
            if res <= 0:
                self.memo[i][j] = 1
            else:
                self.memo[i][j] = res
            return self.memo[i][j]

    dp = DP(m, n)
    r = dp.run(grid, 0, 0)
    print(r)
    return r


def maxCoins(nums):
    '''
    有 n 个⽓球,编号为 0 到 n - 1,每个⽓球上都标有⼀个数字,这些数字存在数组 nums 中。
    现在要求你戳破所有的⽓球。戳破第 i 个⽓球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1]枚硬币。
    这⾥的 i - 1 和 i + 1 代表和 i 相邻的两个⽓球的序号。如果 i - 1 或 i + 1 超出了数组的边
    界,那么就当它是⼀个数字为 1 的⽓球。
    求所能获得硬币的最⼤数量。
    leetcode: 312. 戳⽓球
    input:nums = [3,1,5,8]
    output:167
    思路:
        1. 转换问题为在一排ponits中[0...n+1],戳破[1...n]个气球最多能得多少分
        2. dp[i][j]=x,戳破气球i核气球j之间的所有气球,最高分数为x
        3. base case dp[i][j]=0  0<=i<=n+1  j<=i+1,此时(i,j)之间没有气球
        4. 最终需要求取dp[0][n+1]
        5. 转移方程dp[i][j] = dp[i][k] + dp[k][j]+points[i]*points[k]*points[j]
        6. 这题还涉及如何遍历的问题,需要好好总结下斜着遍历,从后往前遍历
    '''
    n = len(nums)
    points = [0 for _ in range(n + 2)]
    points[0] = points[n + 1] = 1
    for i in range(1, n + 1):
        points[i] = nums[i - 1]

    dp = [[0 for _ in range(n + 2)] for _ in range(n + 2)]
    for i in range(n + 1, -1, -1):  # [n+1...0]
        for j in range(i + 1, n + 2):  # [i+1...n+1]
            for k in range(i + 1, j):  # [i+1...j-1]
                dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[k] * points[j])
    print(dp)
    return dp[0][n + 1]


def findRotateSteps(ring, key):
    '''
    给定⼀个字符串 ring,表示刻在外环上的编码;给定另⼀个字符串 key,表示需要拼写的关键词。你需要算出能够拼写关键词中所有字符的最少步数。
    最初,ring的第⼀个字符与 12¸00 ⽅向对⻬。你需要顺时针或逆时针旋转ring以使 key的⼀个字符在 12¸00 ⽅向对⻬,然后按下中⼼按钮,以此逐个拼写完 key中的所有字符。
    旋转 ring拼出 key 字符 **key[i]**的过程中:
        1、你可以将 ring顺时针或逆时针旋转⼀个位置,计为 1 步。旋转的最终⽬的是将字符串ring的⼀个字符与12¸00 ⽅向对⻬,并且这个字符必须等于字符 key[i]。
        2、如果字符 **key[i]**已经对⻬到 12¸00 ⽅向,你需要按下中⼼按钮进⾏拼写,这也将算作 1 步。按完之后,你可以开始拼写 key的下⼀个字符(下⼀阶段),直⾄完成所有拼写。
    leetcode: 514. ⾃由之路
    input: ring = "godding", key = "gd"
        对于 key 的第⼀个字符 'g',已经在正确的位置,我们只需要 1 步来拼写这个字符。
        对于 key 的第⼆个字符 'd',我们需要逆时针旋转 ring "godding" 2 步使它变成"ddinggo"。
        当然,我们还需要 1 步进⾏拼写。
        因此最终的输出是 4。
    output: 4
    思路:
        1. 问题转化:圆盘固定,我们可以拨动指针;现在需要我们拨动指针并按下按钮,以最少的操作次数输入key对应的字符串。
        2.
        3.
    '''


def minDistance(s1, s2):
    '''
    两个字符串的删除操作
    给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最⼩步数,每步可以删除任意⼀个字符串中的⼀个字符。
    leetcode: 583. 两个字符串的删除操作
    input:"sea", "eat"
    output:2
    思路:
        1. 问题转化:删除的最少次数,即最长公共子序列
        2.
        3.
    '''

    class DP:
        def __init__(self, s1, s2):
            self.s1 = s1
            self.s2 = s2
            self.m = len(s1)
            self.n = len(s2)
            self.memo = [[-1 for _ in range(self.n)] for _ in range(self.m)]

        def run(self, i, j):
            # run(s1, i, s2, j)计算s1[i..]和s2[j..]的最长公共子序列长度。
            if i == self.m or j == self.n:
                return 0
            if self.memo[i][j] != -1:
                return self.memo[i][j]
            if self.s1[i] == self.s2[j]:
                self.memo[i][j] = self.run(i + 1, j + 1) + 1
            else:
                self.memo[i][j] = max(self.run(i + 1, j), self.run(i, j + 1))

            return self.memo[i][j]

    dp = DP(s1, s2)
    res = dp.run(0, 0)
    print(res)
    return len(s1) - res + len(s2) - res


def longestCommonSubsequence(s1, s2):
    '''
    给定两个字符串 text1 和 text2,返回这两个字符串的最⻓公共⼦序列的⻓度。如果不存在公共⼦序列,返回 0。
    ⼦序列的定义:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
    两个字符串的公共⼦序列是这两个字符串所共同拥有的⼦序列。
    leetcode: 1143. 最⻓公共⼦序列
    input:text1 = "abcde", text2 = "ace"
    output:3
    思路:
        1. 自底向上:
            1) 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j]
            2) 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n]
            3) base case: dp[0][..] = dp[..][0] = 0
        2. 自顶向下
            1) 定义:dp(s1, i, s2, j)
            2) 目标:dp(s1, 0, s2, 0) 计算 s1[0..] 和 s2[0..] 的 lcs 长度
            3) base case:i, j 到头,然后返回0
        3. 公共部分
            1) s1[i]和s2[j]相等的时候,各往前1步
            2) s1[i]和s2[j]不相等,要么s1[i]是公共部分,要么s2[j]是公共部分,要么s1[i]和s2[j]都不是公共部分
                其中第三种情况排除,
    '''
    m, n = len(s1), len(s2)
    dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i - 1] == s2[j - 1]:
                dp[i][j] = 1 + dp[i - 1][j - 1]
            else:
                dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
    print(dp[m][n])
    return dp[m][n]


def minimumDeleteSum(s1, s2):
    '''
    两个字符串的最⼩ ASCII 删除和
    给定两个字符串 s1, s2,找到使两个字符串相等所需删除字符的 ASCII 值的最⼩和。
    leetcode:712. 两个字符串的最⼩ ASCII 删除和
    input:s1 = "sea", s2 = "eat ; s1 = "delete", s2 = "leet"
    output:231; 403
    思路:
        1. 问题转换:求删除最小字符串个数的ascii码的累加和
        2.
        3.
    '''

    class DP:
        def __init__(self, s1, s2):
            self.s1 = s1
            self.s2 = s2
            self.m = len(s1)
            self.n = len(s2)
            self.memo = [[-1 for _ in range(self.n)] for _ in range(self.m)]

        def run(self, i, j):
            res = 0
            # 当一个字符串扫描结束后,另一个字符串的剩余字符都要删除
            if i == self.m:
                while j != self.n:
                    res += ord(self.s2[j])
                    j += 1
                return res
            if j == self.n:
                while i != self.n:
                    res += ord(self.s1[i])
                    i += 1
                return res

            if self.memo[i][j] != -1:
                return self.memo[i][j]

            if self.s1[i] == self.s2[j]:
                self.memo[i][j] = self.run(i + 1, j + 1)
            else:
                self.memo[i][j] = min(ord(self.s1[i]) + self.run(i + 1, j), ord(self.s2[j]) + self.run(i, j + 1))

            return self.memo[i][j]


def findCheapestPrice(n, flights, src, dst, k):
    '''
    有 n 个城市通过⼀些航班连接。
    给你⼀个数组 flights,其中 flights[i] = [fromi, toi, pricei],表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
    现在给定所有的城市和航班,以及出发城市 src 和⽬的地 dst,你的任务是找到出⼀条最多经过 k 站中转的路线,
    使得从 src 到 dst 的价格最便宜,并返回该价格。如果不存在这样的路线,则输出 -1
    leetcode: 787. K 站中转内最便宜的航班
    input:n = 3, edges = [
                            [0,1,100],
                            [1,2,100],
                            [0,2,500]
                         ]
           src = 0, dst = 2, k = 1
    output:200
    思路:
        1. 这题就是个加权有向图中求最短路径的问题
        2. 定义dp(s, k),从起点src出发,k步之内到达节点s的最小路径权重为dp(d, k)
        3.
    '''
    import math
    k += 1  # 中转站的个数转换为边数

    class DP:
        def __init__(self, n, flights, k):
            # 备忘录的初始值为-2
            self.memo = [[-2 for _ in range(k + 1)] for _ in range(n)]
            self.indegree = dict()
            for v in flights:
                _from, _to, _price = v
                if _to not in self.indegree:
                    self.indegree[_to] = []
                self.indegree[_to].append((_from, _price,))

        def run(self, s, _k):
            # base case:入度的dst和src是同一个,则price自然为0
            if s == src:
                return 0

            # 中转边数为0,那么自然无解
            if _k == 0:
                return -1

            if self.memo[s][_k] != -2:
                return self.memo[s][_k]
            res = math.inf
            if s in self.indegree:
                # 遍历所有入度,取连接的入度的price最小的值,如果没有入度,则没有解
                for v in self.indegree[s]:
                    _from, _price = v
                    sub_res = self.run(_from, _k - 1)
                    if sub_res != -1:
                        res = min(res, sub_res + _price)
            self.memo[s][k] = res
            return -1 if res == math.inf else res

    dp = DP(n, flights, k)
    r = dp.run(dst, k)
    print(r)
    return r


def superEggDrop(k, n):
    '''
    给你 k 枚相同的鸡蛋,并可以使⽤⼀栋从第 1 层到第 n 层共有 n 层楼的建筑。
    已知存在楼层 f,满⾜ 0 <= f <= n,任何从⾼于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或⽐它低的楼层落下的鸡蛋都不会破。
    每次操作,你可以取⼀枚没有碎的鸡蛋并把它从任⼀楼层 x 扔下(满⾜ 1 <= x <= n)。
    如果鸡蛋碎了,你就不能再次使⽤它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中重复使⽤这枚鸡蛋。
    请你计算并返回确定 f 的最⼩操作次数是多少?
    leetcode:887. 鸡蛋掉落
    input:k = 1, n = 2
    output:2
    思路:
        1. https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247484675&idx=1&sn=4a4ac1c0f1279530b42fedacc6cca6e6&chksm=9bd7fb0baca0721dda1eaa1d00b9a520672dc9d5c3be762eeca869be35d7ce232922ba8e928b&scene=21#wechat_redirect
        2. https://mp.weixin.qq.com/s/7XPGKe7bMkwovH95cnhang
        3.
    '''


def minFallingPathSum(matrix):
    '''
    给你⼀个 n x n 的整数数组 matrix,请你找出并返回 matrix 的下降路径的最⼩和。
    下降路径可以从第⼀⾏中的任何元素开始,并从每⼀⾏中选择⼀个元素。
    在下⼀⾏选择的元素和当前⾏所选元素最多相隔⼀列(即位于正下⽅或者沿对⻆线向左或者向右的第⼀个元素,可类⽐俄罗斯⽅块)。
    具体来说,位置 (row, col) 的下⼀个元素应当是 (row + 1, col - 1) 、 (row + 1, col) 或者 (row + 1, col + 1)。
    leetcode:931. 下降路径最⼩和
    input:matrix = [[2,1,3],[6,5,4],[7,8,9]]
    output:13
    思路:
        1.
        2.
        3.
    '''
    import math
    n, res = len(matrix), math.inf

    class DP:
        def __init__(self, matrix, n):
            self.matrix = matrix
            self.n = n
            self.memo = [[math.inf for _ in range(self.n)] for _ in range(self.n)]
            self.res = math.inf

        def run(self, i, j):
            # 边界条件 返回最大值
            if i < 0 or j < 0 or i >= self.n or j >= self.n:
                return math.inf

            # base case 返回第一层各列的数据
            if i == 0:
                return self.matrix[0][j]

            if self.memo[i][j] != math.inf:
                return self.memo[i][j]

            # 当前累加值取决于正上,左上,右上的数据
            self.memo[i][j] = self.matrix[i][j] + min(self.run(i - 1, j),
                                                      self.run(i - 1, j - 1),
                                                      self.run(i - 1, j + 1))
            return self.memo[i][j]

    dp = DP(matrix, n)
    # 遍历以最后一层各个值作为路径的最后一个值的情况
    for j in range(n):
        res = min(res, dp.run(n - 1, j))
    print(res)
    return res


if __name__ == "__main__":
    # isMatch("aa", "a")
    # isMatch("aa", "a*")
    # isMatch("aa", "a*.")
    # isMatch("aa", "a*b*c*")
    # isMatch("aa", "a*aa")
    # isMatch("aa", "a*aaa")
    # isMatch("caa", ".a*")
    # minPathSum([
    #     [1, 3, 1],
    #     [1, 5, 1],
    #     [4, 2, 1]
    # ])
    # minPathSum([[2]])
    # minDistance("horse", "ros")
    # maxProfitWithK1([7, 1, 5, 3, 6, 4])
    # maxProfitWithKN([7, 1, 5, 3, 6, 4])
    # maxProfitWithK2([3, 3, 5, 0, 0, 3, 1, 4])
    # maxProfitWithK([2, 4, 1], 2)
    # maxProfitWithK([3, 3, 5, 0, 0, 3, 1, 4], 3)
    # maxProfitWithFee([1, 3, 2, 8, 4, 9], 2)
    # calculateMinimumHP(
    #     [
    #         [-2, -3, 3],
    #         [- 5, -10, 1],
    #         [10, 30, -5]
    #     ]
    # )
    # maxCoins([1, 5, 6])
    # minDistance('eat', 'sea')
    # longestCommonSubsequence('abcde', 'ace')
    # findCheapestPrice(3, [
    #     [0, 1, 100],
    #     [1, 2, 100],
    #     [0, 2, 500]
    # ], 0, 2, 1)
    minFallingPathSum([
        [2, 1, 3],
        [6, 5, 4],
        [7, 8, 9]
    ])

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值