[oj.leetcode] #174 - Dungeon Game 一次特别的DP之旅

来源:互联网 发布:淘宝 大麦网 编辑:程序博客网 时间:2024/05/01 00:15

原题篇幅挺长,关于一个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)。

0 0
原创粉丝点击