【重拾算法~Leetcode每日一题】174. 地下城游戏(难度:困难)

174. 地下城游戏
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

-2 -3 3
-5 -10 1
10 30 -5

简单概括:

  1. 骑士只能往右走 or 往下走
  2. 经过对应的房间会加血 or 扣血
  3. 需要始终保持骑士血量 > 0

这道题的难度应该是M,因为只需要一个从右下往左上的动态规划即可,看了看评论没做出来的应该是想着从左上往右下动态规划了,这样需要维持两个条件:①骑士不能死(起始最小血量)②累积扣血最少
举个例子,可能某条路径总计只扣血1滴,但是路径中可能需要先扣10000滴血再给你加9999滴血,那么你累积扣血可能比较少,但是骑士最小初始血量就很大了,因此从后往前动规比较方便,可以把两者一起考虑。
既然想清楚思路了,只需要建立一个动态转移方程,由于骑士比较乖,不上下左右乱跑,所以我们在某处的动态转移方程只需要考虑其往右走和往下走两个状态即可(假设骑士在i行j列处所需最小血量为:F[ i ][ j ]),那么F[ i ][ j ]取决于F[ i+1] [ j ] 和F[ i ] [ j+1 ](血量最少原则)
当前状态所需最小血量 = 下一状态所需最小血量 - 当前状态消耗血量
另外需要注意的是,如果当前状态给你加了血,那么这个当前状态所需最小血量可能是个负数,但因为要保证不死,最小血量应>1
所以改一下:
当前状态所需最小血量 = max(下一状态所需最小血量 - 当前状态消耗血量 , 1)
换成函数表示就是:
F[i][j] = max( min(F[i+1][j],F[i][j+1)-cost , 1 )
有了状态转移方程,大胆写代码就好啦(记得考虑边界条件):

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        m,n = len(dungeon),len(dungeon[0])
        dp = [[0 for i in range(n)] for i in range(m)]
        dp[-1][-1] = max (1,1-dungeon[-1][-1])
        for i in range (m-2,-1,-1):
            dp[i][-1] = max(1, dp[i+1][-1] - dungeon[i][-1])
        for j in range (n-2,-1,-1):
            dp[-1][j] = max(1,dp[-1][j+1] - dungeon[-1][j])
        for i in range(m-2,-1,-1):
            for j in range (n-2,-1,-1):
                dp[i][j] = max(1, min(dp[i+1][j],dp[i][j+1])-dungeon[i][j])
        return dp[0][0]

因为我一开始初始化的之后只初始化了M*N大小,所以就多用了两个for循环来避免边界问题,代码显得有点丑陋,如果想更改的话可以多初始化一行+一列的数组,并赋一个比较大的值,避免在边界的时候出现bug,我急着去背单词,就不改啦嘻嘻
【重拾算法~Leetcode每日一题】174. 地下城游戏(难度:困难)_第1张图片

你可能感兴趣的:(python,动态规划,算法)