动态规划的应用(六):矩阵相关问题

本文以 LeetCode 第 174 题为例,介绍动态规划算法解决与字符串相关的问题。
LeetCode 174. Dungeon Game(地下城游戏)

其他动态规划的应用实例:
动态规划的应用(一):最短路问题
动态规划的应用(二):cutting stock 问题
动态规划的应用(三):字符串相关问题
动态规划的应用(四):LeetCode 1900. 最佳运动员的比拼回合
动态规划的应用(五):LeetCode 413, 446. 等差数列划分


LeetCode 174. Dungeon Game(地下城游戏)


问题描述

LeetCode 174 I
LeetCode 174 II

思路与代码


本题的思路很容易看出是动态规划,但仔细分析会发现,一条路径所需要的体力值并不是路径最终体力变化这么简单,具体分析可以参见官方题解:
LeetCode 174 官方题解 I
LeetCode 174 官方题解 II

因此,自然想到只能通过从终点向前反向递归的方式,实现动态规划算法。

于是,笔者提交了如下代码:

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        def dp(mat_cost: List[List[int]], i: int, j: int) -> int:
            m, n = len(mat_cost), len(mat_cost[0])  # 矩阵大小

            if i == m - 1 and j == n - 1:  # 终点
                blood_need = 1 - mat_cost[-1][-1] if mat_cost[-1][-1] < 0 else 1
                return blood_need

            list_blood_need = []
            if i < m - 1:  # 向右移动
                blood_need_route = dp(mat_cost=mat_cost, i=i + 1, j=j)
                blood_need = blood_need_route - mat_cost[i][j] if blood_need_route - mat_cost[i][j] > 0 else 1
                list_blood_need.append(blood_need)
            if j < n - 1:  # 向下移动
                blood_need_route = dp(mat_cost=mat_cost, i=i, j=j + 1)
                blood_need = blood_need_route - mat_cost[i][j] if blood_need_route - mat_cost[i][j] > 0 else 1
                list_blood_need.append(blood_need)
            blood_need = min(list_blood_need)

            return blood_need

        return dp(mat_cost=dungeon, i=0, j=0)

然而,在运行后面的算例时出现了超时,超时算例为一个 60 × 70 60 \times 70 60×70 的矩阵:
LeetCode 174 超时算例

反思代码的实现方式,发现犯了一个低级错误,即动态规划的优势是避免重复搜索,然而前面的代码并没有存储到达每个点的中间结果。于是,笔者又将代码修改如下:

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        def dp(mat_cost: List[List[int]], i: int, j: int, dict_res: Dict[Tuple[int, int], int]) -> int:
            m, n = len(mat_cost), len(mat_cost[0])  # 矩阵大小

            if i == m - 1 and j == n - 1:  # 终点
                blood_need = 1 - mat_cost[-1][-1] if mat_cost[-1][-1] < 0 else 1
                dict_res[i, j] = blood_need  # 存储中间结果
                return blood_need

            if (i, j) in dict_res.keys():  # 查询中间结果
                return dict_res[i, j]

            list_blood_need = []
            if i < m - 1:  # 向右移动
                blood_need_route = dp(mat_cost=mat_cost, i=i + 1, j=j, dict_res=dict_res)
                blood_need = blood_need_route - mat_cost[i][j] if blood_need_route - mat_cost[i][j] > 0 else 1
                list_blood_need.append(blood_need)
            if j < n - 1:  # 向下移动
                blood_need_route = dp(mat_cost=mat_cost, i=i, j=j + 1, dict_res=dict_res)
                blood_need = blood_need_route - mat_cost[i][j] if blood_need_route - mat_cost[i][j] > 0 else 1
                list_blood_need.append(blood_need)
            blood_need = min(list_blood_need)
            dict_res[i, j] = blood_need  # 存储中间结果

            return blood_need

        return dp(mat_cost=dungeon, i=0, j=0, dict_res={})

重新提交运行,终于通过了,然而运行效果不太理想:
LeetCode 174 运行效果 1

再次参考官方题解反思,发现并不需要通过递归的方式实现,只需要从终点(右下角)向起点(左上角)反向遍历即可。

LeetCode 174 官方题解

LeetCode 174 官方题解 III
LeetCode 174 官方题解 IV

代码如下:

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        n, m = len(dungeon), len(dungeon[0])

        big_m = 10 ** 8
        dp = [[big_m] * (m + 1) for _ in range(n + 1)]  # 增加一行一列,以便从终点开始
        dp[n][m - 1] = dp[n - 1][m] = 1  # 边界外增加的一行一列置为 1
        for i in range(n - 1, -1, -1):
            for j in range(m - 1, -1, -1):
                min_blood = min(dp[i + 1][j], dp[i][j + 1])
                dp[i][j] = max(min_blood - dungeon[i][j], 1)

        return dp[0][0]

运行效果:
LeetCode 174 运行效果 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值