一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
一.题目:
174. 地下城游戏 一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。 我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。 有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
二、思路分析:
因为题目是要求从左上角到右下角的最小健康点数,且我们每走一步都要保持健康点数是正整数,因为如果健康点数降至0或以下就会立即死亡,所以我们这道题的基本思路为:
-
首先确定
dp的定义:我们定义dp[i][j]为从(i,j)到右下角所需的最小健康值,最终的结果我们直接返回dp[0][0]即可。这样的定义是最符合题目要求的,所以我们需要根据题目的具体要求来改变我们的定义。 -
进行初始化,我们初始化右下角的值,如果他的点数是正的说明不用需要额外的健康点数
dp直接置为0,如果是小于0的说明需要点数+1。 -
确定状态:题目中要求只能够往下走或者往右走,所以我们写代码的时候是从顶部到底部的,即我们是反方向的只能往上走或者往左走。
-
确定状态转移方程:我们只需要选择往右走或者往下走的最小值即可,代码为
Math.min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]。
三、代码:
/**
* @param {number[][]} dungeon
* @return {number}
*/
var calculateMinimumHP = function (dungeon) {
//dp[i][j]定义为从(i,j)到右下角所需的最小健康值
let m = dungeon.length
let n = dungeon[0].length
let dp = Array.from(new Array(m+1), () => new Array(n+1))
//初始化
dp[m - 1][n - 1] = dungeon[m - 1][n - 1] < 0 ? -dungeon[m - 1][n - 1] + 1 : 1
for (let i = m ; i >= 0; i--) {
for (let j = n ; j >= 0; j--) {
if(i == m || j == n){
dp[i][j] = Number.MAX_SAFE_INTEGER
continue
}
if (i == m - 1 && j == n - 1) continue
//状态转移方程
let res = Math.min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]
dp[i][j] = res <= 0 ? 1 : res
}
}
return dp[0][0]
};
四、总结:
这道题目告诉我们,我们需要准确找到题目的要求并且定义
dp符合题目的要求,这种动态规划的变体题目需要深入进去,但其实原理是一样的不过需要转变一下自己的思路。要学会使用逆向思维,这道题目用到了逆向思维,即从最右下角往起点走。