leetcode刷题笔记一百七十四题 地下城游戏
源地址:174. 地下城游戏
问题描述:
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
-2 (K) -3 3
-5 -10 1
10 30 -5 (P)说明:
骑士的健康点数没有上限。
任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
//记忆回溯法 -- 直接使用回溯法由于重复访问会超时,需要使用记忆数组
object Solution {
def calculateMinimumHP(dungeon: Array[Array[Int]]): Int = {
//构建记忆数组
val meno = Array.fill(dungeon.length, dungeon(0).length)(-1)
//回溯 从(i, j)点开始到达公主处的最小血量
def dfs(dungeon: Array[Array[Int]], row: Int, col: Int, i: Int, j: Int): Int = {
//回溯中止条件
//若dungeon(i)(j)的权值为正,则只需1即可到达
//否则为1-dungeon(i)(j)
if (i == row-1 && j == col-1) return Math.max(1 - dungeon(i)(j), 1)
//已访问过的, 直接返回
if (meno(i)(j) > 0) return meno(i)(j)
var min = 0
//处于最后一行 只考虑右侧的最优走法
if (i == row-1) min = Math.max(dfs(dungeon, row, col, i, j+1)-dungeon(i)(j), 1)
//处于最后一列 只考虑下侧的最优走法
else if (j == col-1) min = Math.max(dfs(dungeon, row, col, i+1, j)-dungeon(i)(j), 1)
//否则取两者中最优解
else min = Math.max(Math.min(dfs(dungeon, row, col, i, j+1), dfs(dungeon, row, col, i+1, j))-dungeon(i)(j), 1)
//记录记忆数组
meno(i)(j) = min
return meno(i)(j)
}
return dfs(dungeon, dungeon.length, dungeon(0).length, 0, 0)
}
}
//(反向)动态规划
//最优子结构 dp(i)(j) = Math.min(dp(i+1)(j), dp(i)(j+1)) - dungeon(i)(j)
object Solution {
def calculateMinimumHP(dungeon: Array[Array[Int]]): Int = {
if (dungeon == null || dungeon.length == 0 || dungeon(0).length == 0) return 0
val rowLength = dungeon.length
val colLength = dungeon(0).length
val dp = Array.fill(rowLength, colLength)(0)
//设置最后一个值
dp(rowLength-1)(colLength-1) = Math.max(0, -dungeon(rowLength - 1)(colLength - 1))
//设置最后一列
for (i <- (0 to rowLength-2).reverse){
var needMin = dp(i+1)(colLength-1) - dungeon(i)(colLength-1)
dp(i)(colLength-1) = Math.max(0, needMin)
}
//设置最后一行
for (i <- (0 to colLength-2).reverse){
var needMin = dp(rowLength-1)(i+1) - dungeon(rowLength-1)(i)
dp(rowLength-1)(i) = Math.max(0, needMin)
}
//反向动态规划
for (i <- (0 to rowLength-2).reverse){
for (j <- (0 to colLength-2).reverse){
var needMin = Math.min(dp(i+1)(j), dp(i)(j+1)) - dungeon(i)(j)
dp(i)(j) = Math.max(0, needMin)
}
}
return dp(0)(0) + 1
}
}