[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)。
- [oj.leetcode] #174 - Dungeon Game 一次特别的DP之旅
- Leetcode OJ Dungeon Game
- Leetcode 174 - Dungeon Game(二分+dp)
- leetcode之Dungeon Game
- leetcode 174: Dungeon Game
- [leetcode 174] Dungeon Game
- LeetCode(174) Dungeon Game
- LeetCode 174 Dungeon Game
- LeetCode #174 Dungeon Game
- leetcode 174: Dungeon Game
- [LeetCode 174] Dungeon Game
- LeetCode 174 Dungeon Game
- Leetcode 174 Dungeon Game
- leetcode -- Dungeon Game -- dp重点,典型题
- Leetcode—174. Dungeon Game 倒序DP
- [leetcode-174]Dungeon Game(java)
- LeetCode 174 Dungeon Game 题解
- leetcode 174. Dungeon Game 一个逆着推导计算的DP动态规划问题
- CSS学习笔记
- UVa 11040 - Add bricks in the wall(规律)
- java基础--数组
- Ojbective-C为什么不用@public
- HDU 2483 Counting square
- [oj.leetcode] #174 - Dungeon Game 一次特别的DP之旅
- hdu 1026 Ignatius and the Princess I
- Activity学习小结
- POJ 1276 Cash Machine
- Linux信号实践(2) --信号分类
- [轻松一刻] IT人的工资是这个样子滴
- 医疗行业大数据医疗分析案例
- jQuery on()方法
- MVC、KVO、KVC