一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
-2 (K) | -3 | 3 |
---|---|---|
-5 | -10 | 1 |
10 | 30 | -5 (P) |
说明:
- 骑士的健康点数没有上限。
- 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
一、分析
骑士见公主游戏,游戏的规则如下:
骑士从左上角出发,每次只能向右或向下走,最后到达右下角见到公主。地图中每个位置的值代表骑士要遭遇的事情。如果是负数,说明此处有怪兽,要让骑士损失血量。如果是非负数,代表此处有血瓶,能让骑士回血。骑士从左上角到右下角的过程中,走到任何一个位置时,血量都不能少于1。为了保证骑士能见到公主,初始血量至少是多少?根据map,返回至少的初始血量。
方法一:暴力递归,二话不说,一切从尝试入手,先来个暴力解,定义process的含义:从矩阵左上角出发,到达右下角,返回至少的初始血量(至少需要多少血量才能到大右下角,见到公主)。
首先分析 base case,就是骑士最终到大位置(右下角),分析可能性:
- 如果
maxtrix[N - 1][M - 1]
位置上是负数,血量至少是相反数加1,比如血量是-3,那么至少需要血量4,很显然吧,在到大右下角前的血量是4,到达右下角时,需要付出3滴血,所以剩下1滴血就能见到公主。 - 如果
maxtrix[N - 1][M - 1]
位置上是非负数(大于等于0),在到达右下角时,只需要1滴血就够了,保证能登上去就行,最省的血量。
其次骑士只能向右或向下走
假如骑士来到了最后一行上,只能向右走,骑士需要右边的格子告诉它需要多少的血量才能向右走,右边调递归,把右边递归返回来的值记为rightNeed,分析可能性如下:
- 如果当前格子
matrix[row][col]
的血量 < 0,则需要-matrix[row][col]
+ rightNeed 的血 - 如果当前格子
matrix[row][col]
的血量 ≥ rightNeed,则需要1滴血就行,能登上右边格子就行 - 如果当前格子
matrix[row][col]
的血量 < rightNeed,则需要 rightNeed -matrix[row][col]
的血
注意:rightNeed 不可能为负数,如果为负数怎么也到达不了右下角,遇见公主
假如骑士来到了最后一列上,只能向下走,骑士需要下边的格子告诉它需要多少的血量才能向下走,下边调递归,把下边递归返回来的值记为downNeed,分析可能性如下:
- 如果当前格子
matrix[row][col]
的血量 < 0,则需要-matrix[row][col]
+ downNeed 的血 - 如果当前格子
matrix[row][col]
的血量 ≥ downNeed,则需要1滴血就行,能登上右边格子就行 - 如果当前格子
matrix[row][col]
的血量 < downNeed,则需要 downNeed -matrix[row][col]
的血
注意:downNeed 不可能为负数,如果为负数怎么也到达不了右下角,遇见公主
骑士来到其他位置的格子上,因为骑士只能向下或向右走,哪个方向需要的血量最少走哪个方向,记为minNextNeed,分析可能性如下:
- 如果当前格子
matrix[row][col]
的血量 < 0,则需要-matrix[row][col]
+ minNextNeed 的血 - 如果当前格子
matrix[row][col]
的血量 ≥ minNextNeed,则需要1滴血就行,能登上右边格子就行 - 如果当前格子
matrix[row][col]
的血量 < minNextNeed,则需要 minNextNeed -matrix[row][col]
的血
方法二:动态规划,dp就简单啦,由暴力递归改动态规划,那是水到渠成的,递归怎么写的,dp表就怎么填就行,分析递归参数,哪些是可变参数,就是row和col,其他参数都是不变的,所以准备二维dp表。
二、实现
1、暴力递归
public int calculateMinimumHP(int[][] dungeon) {
return process(dungeon, dungeon.length, dungeon[0].length, 0, 0);
}
// 来到了matrix[row][col],还没登上去,到达右下角,返回至少的初始血量
public static int process(int[][] matrix, int N, int M, int row, int col) {
if (row == N - 1 && col == M - 1) { // base case 已经达到右下角了
// -3 4 如果是负数,则必须是 相反数+1 才能登上去
// 7 1 如果是正数,则必须保证1滴血才能登上去
return matrix[N - 1][M - 1] < 0 ? (-matrix[N - 1][M - 1] + 1) : 1;
}
if (row == N - 1) { // 来到了最后一行上
int rightNeed = process(matrix, N, M, row, col + 1);
if (matrix[row][col] < 0) { // 3 -7 10
return -matrix[row][col] + rightNeed;
} else if (matrix[row][col] >= rightNeed) { // 3 3 1
return 1;
} else { // 3 1 2
return rightNeed - matrix[row][col];
}
}
if (col == M - 1) { // 来到了最后一列上
int downNeed = process(matrix, N, M, row + 1, col);
if (matrix[row][col] < 0) { // 3 -7 10
return -matrix[row][col] + downNeed;
} else if (matrix[row][col] >= downNeed) { // 3 3 1
return 1;
} else { // 3 1 2
return downNeed - matrix[row][col];
}
}
// 其他位置
int minNextNeed = Math.min(process(matrix, N, M, row, col + 1), process(matrix, N, M, row + 1, col));
if (matrix[row][col] < 0) { // 3 -7 10
return -matrix[row][col] + minNextNeed;
} else if (matrix[row][col] >= minNextNeed) { // 3 3 1
return 1;
} else { // 3 1 2
return minNextNeed - matrix[row][col];
}
}
2、动态规划
public int calculateMinimumHP(int[][] dungeon) {
if (dungeon == null || dungeon.length == 0 || dungeon[0] == null || dungeon[0].length == 0) {
return 1;
}
int row = dungeon.length;
int col = dungeon[0].length;
int[][] dp = new int[row--][col--];
dp[row][col] = dungeon[row][col] > 0 ? 1 : -dungeon[row][col] + 1; // 右下角位置(公主位置)
for (int j = col - 1; j >= 0; j--) { // 最后一行格子
dp[row][j] = Math.max(dp[row][j + 1] - dungeon[row][j], 1);
}
int right = 0;
int down = 0;
for (int i = row - 1; i >= 0; i--) {
dp[i][col] = Math.max(dp[i + 1][col] - dungeon[i][col], 1);
for (int j = col - 1; j >= 0; j--) {
right = Math.max(dp[i][j + 1] - dungeon[i][j], 1);
down = Math.max(dp[i + 1][j] - dungeon[i][j], 1);
dp[i][j] = Math.min(right, down);
}
}
return dp[0][0];
}