原题篇幅挺长,关于一个2D关卡游戏,这里以矩阵的方式简单陈述一下。
在一个二维数组M*N中, 有一个王子需要从起点[0][0]出发,移动到终点[m-1][n-1],每次移动一格,方向只能向右或者向下。出发前,王子的(健康)值至少为1。矩阵中每一格有一整型值,可正可负可0,对于经过的王子,会把这值加到他身上。规则是王子在移动过程中(包括到达终点),无论何时他身上的值都不能小于1。问王子在出发前的初始值,最小是多少?
---------------------------------------
先看几个简单例子:
{{ 1, -2},
{-1, -1}}
答案是2,路线是起点开始,往下,往右。
{{ -2, -3, 3}
{ -5, -10, 1}
{10, 30, -5}}
答案是7,路线是起点开始,右,右,下,下。
显然,这题的基本思路是使用动态规划DP。我们需要找到最优子结构。对于每一格,它可能来自左边,或者上边的格子。假设某一格[i][j], 它的既定(健康)值是dungeon[i][j]; 以这一格为终点,从起点出发走到这一格最小的初始值是P[i][j];以P[i][j]为初始值,沿着该路线(最优)经过[i][j]后的值为Sum[i][j]。
这里我们先讨论一个子问题,如果以格子[i][j]为终点,那么它的P和Sum该如何计算?
对于上图的右下格,如果进入它的选项是左边格,它的P = 3 (Sum = 1); 而如果从上边格进入,它的P = 4 (Sum = 2), 所以它应该选择从左边格子进入。
这里我们定义一个概念,叫依赖深度,即当前子结构依赖之前n步的前继。此时的依赖深度为1。
如果这道题到这就结束了,也就是个普通DP。我们再来看一个例子:
这是个3*3矩阵,沿用上一例的最优子结构式,我们在终点右下角得到P = 5。但实际上,如果我们沿着右,右,下,下的方向移动到终点,我们可以得到P = 3。问题出在终点格的上方格[1][2]。对它来说,如果以此为终点,那么应该选择(2, 1)从左边进入它;而如果考虑它对于之后格子的贡献,那么应该选择(3, 4),从上方进入它。
实际上,这已经揭示了一个全新的最优子结构。对于每一个格子来说,到达它的所谓最优路线,必须要考虑两种需求:以它自己为终点的,和对于后续格子作贡献的。对于前者,我们希望在Sum相等时,P尽可能小;而对于后者,则是在P相等时,Sum尽可能大。这时,该子结构的依赖深度是2。
修正过的处理中矩阵如下图:
这里我们可以很清楚的看到,对于终点格[2][2], 符合条件的最小初始值P 应该等于3。
C++代码实现一份:
class Solution{
public:
int calculateMinimumHP(vector<vector<int> > &dungeon){
int n = dungeon.size();
if(n == 0) return 0;
int m = dungeon[0].size();
vector<vector<int> > horiRow;
vector<vector<int> > leftBound;
vector<int> startRoom;
int p0 = 0, sum0 = 0;
nextStep(1, 1, 1, 1, dungeon[0][0], p0, sum0);
startRoom.push_back(p0);
startRoom.push_back(sum0);
startRoom.push_back(p0); // room[0][0] has no predecessor
startRoom.push_back(sum0);
horiRow.push_back(startRoom);
leftBound.push_back(startRoom);
for(int j=1; j<m; j++){ // for row[0]
int p1 = horiRow[j-1][0];
int sum1 = horiRow[j-1][1];
int p2 = horiRow[j-1][2];
int sum2 = horiRow[j-1][3];
int np = 0, nsum = 0;
nextStep(p1, sum1, p2, sum2, dungeon[0][j], np, nsum);
vector<int> room;
room.push_back(np);
room.push_back(nsum);
room.push_back(np); // for row[0], every room has only one predecessor
room.push_back(nsum);
horiRow.push_back(room);
}
for(int i=1; i<n; i++){ // for column[0]
int p1 = leftBound[i-1][0];
int sum1 = leftBound[i-1][1];
int p2 = leftBound[i-1][2];
int sum2 = leftBound[i-1][3];
int np = 0, nsum = 0;
nextStep(p1, sum1, p2, sum2, dungeon[i][0], np, nsum);
vector<int> room;
room.push_back(np);
room.push_back(nsum);
room.push_back(np); // for column[0], every room has only one predecessor
room.push_back(nsum);
leftBound.push_back(room);
}
for(int i=1; i<n; i++){
horiRow[0].clear();
for(int k = 0; k < 4; k++){
horiRow[0].push_back(leftBound[i][k]);
}
for(int j=1; j<m; j++){
int np1 = 0, nsum1 = 0, np2 = 0, nsum2 = 0;
nextStep(horiRow[j][0],
horiRow[j][1],
horiRow[j][2],
horiRow[j][3],
dungeon[i][j],
np1,
nsum1);
nextStep(horiRow[j-1][0],
horiRow[j-1][1],
horiRow[j-1][2],
horiRow[j-1][3],
dungeon[i][j],
np2,
nsum2);
horiRow[j].clear();
horiRow[j].push_back(np1);
horiRow[j].push_back(nsum1);
horiRow[j].push_back(np2);
horiRow[j].push_back(nsum2);
}
}
return min(horiRow[m-1][0], horiRow[m-1][2]);
}
private:
/*
* p1: for path1 from predecessor(e.g. up room), min init HP
* sum2: for path1 from predecessor(e.g. up room), sum HP with init HP p1
* p2: for path2 from predecessor(e.g. left room), min init HP
* sum2: for path2 from predecessor(e.g. left room), sum HP with init HP p2
* val: HP value of current room
* np:
* */
void nextStep(int p1, int sum1, int p2, int sum2,
int val, int& np, int& nsum){
int np1 = 0, nsum1 = 0, np2 = 0, nsum2 = 0;
directStep(p1, sum1, val, np1, nsum1);
directStep(p2, sum2, val, np2, nsum2);
if(nsum1 == nsum2){ // key block to determine next step choice
nsum = nsum1;
np = min(np1, np2);
}else if(np1 == np2){
np = np1;
nsum = max(nsum1, nsum2);
}else if((np1 < np2 && nsum1 > nsum2) || (np1 > np2 && nsum1 < nsum2)){
np = min(np1, np2);
nsum = max(nsum1, nsum2);
}else{ // (np1, nsum1) > or < (np2, nsum2)
np = min(np1, np2);
nsum = min(nsum1, nsum2);
}
return;
}
void directStep(int p, int sum, int val, int& np, int& nsum){
int delta = (val >= 0 ? 0 : max(0, 1 - val - sum));
np = p + delta;
nsum = sum + delta + val;
return;
}
};
以上代码已经经过一些优化,通过将中间数组从二维降到一维,空间上复杂度仅为O(n)。