10.3.3 (python) 动态规划数组类LeetCode题目 —— Dungeon Game & Frog Jump

来看两道 hard 级别的题目,其子问题性质很明显,就是动态规划,难点就是我们可能找不好子问题的关系式。

174. Dungeon Game

The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned in the top-left room and must fight his way through the dungeon to rescue the princess.

The knight has an initial health point represented by a positive integer. If at any point his health point drops to 0 or below, he dies immediately.

Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering these rooms; other rooms are either empty (0's) or contain magic orbs that increase the knight's health (positive integers).

In order to reach the princess as quickly as possible, the knight decides to move only rightward or downward in each step.

Write a function to determine the knight's minimum initial health so that he is able to rescue the princess.

For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path RIGHT-> RIGHT -> DOWN -> DOWN.

-2 (K)-33
-5-101
1030-5 (P)

题目解析:

我们要求最小的初始生命值以通过关卡,而限制条件在于任一位置的分数有可能让国王狗带,不仅要保证通过时生命值> 0,也要保证任一位置都>0。

这道题需要从后向前推导以解决问题(考虑问题的场景,理解从后向前的必要性),这样dp[i][j] 代表的是进入i,j处所需最小生命值。

然后我们梳理一下状态转移关系:

dp[i][j] 代表的是进入i,j处所需最小生命值,以终点为例(-5) dp=6, 试想终点分数为5时dp=1,可见dp[i][j] = max(1- dungeon[i][j], 1)。我们进一步考虑dp[i][j]的关系式,我们从i+1,j 和i,j+1两处选择所需生命值最低的路径,然后减去当前的分数 min(dp[j], dp[j+1]) - dungeon[i][j] ,同样也要考虑到结果为负数,说明进入i,j时最低生命值为1即可(因为生命值全程不能为负,即时要加很多分,你初始时刻最低也要是1)。直接看代码,仅仅几行,关键是关系式。

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

 

403. Frog Jump

A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.

Given a list of stones' positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.

If the frog's last jump was k units, then its next jump must be either k - 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.

题目解析:

题目不多介绍,算是升级版的爬楼梯了吧。

场景很简单,但是如何设计问题框架并不容易。dp[i]表示能否到达i处?可以,但是“能否”一词会丢失很多信息,无法判断dp[i+1]所代表的石头能否到达,因为从i到j(注意该问题不一定是从i到i+1)可以走k,k-1,k+1三种步伐,因此k值和子问题息息相关。

因此鄙人的解法,dp[i]为一个列表,表示i处的石头到达时所走的步伐k值(会有多个),以便解决后续问题。

然后我们看dp[i]与dp[j](j<i)之间的关系,若dp[j]有多个k值,k1,k2,k3...,对于每个k值,如果stones[i]和stones[j]之间差距在k-1 ~ k+1 之间,则将stones[i]-stones[j]放入dp[i]中;在鄙人的解法中,引入二分查找,快速定位可能的j值的起始位置(方法还可进一步完善),而不是从0开始每个石头的k值进行比对。代码如下:

class Solution:
    def canCross(self, stones: List[int]) -> bool:
        n = len(stones)
        if stones[1] != 1:
            return False
        
        def bs(arr, lt, rt, target):
            while lt < rt:
                mid = (lt + rt) // 2
                if arr[mid] < target:
                    lt = mid + 1
                else:
                    rt = mid
            return rt
            
        dp = [[] for _ in range(n)] # dp[i] 记录到达i处可能的k值,有可能有多个离散值
        dp[0] = [0]
        dp[1] = [1]
        max_k = 1
        for ix in range(2, n):
            stone = stones[ix]
            st = bs(stones, ix-max_k-1, ix, stone-max_k-1)            
            for x in range(st, ix):
                klist = dp[x]
                gap = stone - stones[x]
                for k in klist:
                    if k-1 <= gap <= k+1:
                        dp[ix].append(gap)
                        max_k = max(max_k, gap)
                        break

        if not dp[-1]:
            return False
        return True

以上是两道hard级别的DP题目,比较有代表性,一维,二维各一个,思路上比不太一样。借助这几道题目扩宽DP的解题思路。

后面是几个DP的专题,欢迎继续品尝。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值