动态规划——Dungeon Game
这又是个题干很搞笑的题目:恶魔把公主囚禁在魔宫的右下角,骑士从魔宫的左上角开始穿越整个魔宫到右下角拯救公主,为了以最快速度拯救公主,骑士每次只能向下或者向右移动一个房间,
每个房间内都有一个整数值,负数表示骑士到当前房间要减少这个生命值,非负数表示骑士到当前房间可以增加这个生命值。骑士的初始生命值是一个正整数,请给出骑士需要的最少的初始生命值。
很明显这是个动态规划的题目,而且这个题目的大体框架很常见,就是对一个二维数组进行遍历同时维护另一个dp二维数组,每个dp的值都是与其左侧和上侧的dp值有关。
由于我习惯于使用动态规划的正序解法,一开始的时候我还是使用的正序解法:
维护两个二维数组opt和dp,opt[i][j]表示骑士从左上角到 ( i , j ) 的最优路径的整个过程中耗血量的最小值(这是个负数),dp[i][j]表示骑士从左上角到 ( i , j )最少的耗血量。
此时的状态转移方程为:
i!=0 且 j!=0 时:如果dp[i][j-1]>dp[i-1][j]时,opt[i][j] = dungeon[i][j] + opt[i][j-1],dp[i][j] = min(opt[i][j],dp[i][j-1])
如果dp[i][j-1]<dp[i-1][j]时,opt[i][j] = dungeon[i][j] + opt[i-1][j],dp[i][j] = min(opt[i][j],dp[i-1][j])
如果dp[i][j-1]==dp[i-1][j]时,opt[i][j] = dungeon[i][j] + opt[i-1][j]>=opt[i-1][j]?opt[i-1][j]:opt[i-1][j],dp[i][j] = min(opt[i][j],dp[i-1][j])
如果 i==0或者j==0处于边界,opt和dp的更新只与其上侧或左侧那个不越界的位置有关,这种情况很简单不再详述
如果采用这种正序解法,最后的输出结果是 dp[i][j]>=0?1:(1-dp[i][j])。
这种正序解法看上去没什么问题,但是在实际测试的时候,LeetCode给出的下面的这个测试用例是无法通过的:

如果使用我上面给出的正序解法,结果为5,路线是down -> right -> right -> down。但实际上,LeetCode上给定的结果是3,路线是right -> right -> down -> down
当时我为此很苦恼,而且花费了很长时间去修改这个解法但是最终失败了。
如果回顾一下动态规划的相关定义:
有很看似非多项式的问题经常可以使用动态规划来实现P的解法。比如比较有名的斐波那契数列、背包问题、集合划分问题等。那这个题目能否也符合使用动态规划的条件呢,我们来分析一下。
首先,能采用动态规划求解的问题一般具有3个条件:
- 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
- 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
虽然我总结出来了状态转移方程,也就是那个递推公式,但是显然大前提第2条已经不满足了,前面的dp已经影响了后面的dp值。到此为止,正序解法我没有找到正解,干脆倒序求解。
倒序解法:
dp[i][j]表示以( i , j )为骑士出发的左上角到达右下角所需的最小初始生命值。
除去边界,dp[i][j]显然仍与dp[i][j+1]和dp[i+1][j]有关:一个来自下侧,一个来自右侧
按道理讲,如果能从(i,j)到(i,j+1)或(i+1,j),应该有 dp[i][j+1] = dungeon[i][j]+dp[i][j] 或 dp[i+1][j] = dungeon[i][j]+dp[i][j],
也就是说 dp[i][j] = dp[i][j+1]-dungeon[i][j] 或 dp[i][j] = dp[i+1][j]-dungeon[i][j],不过在实际推算时是由dp[i][j+1]和dp[i+1][j]计算dp[i][j],而且dungeon[i][j]有可能是很大的正值(就是补血补很多的那种),
dp[i][j]到dp[i][j+1]或dp[i+1][j]可能会计算出负值,也就是说骑士在(i,j)处即使生命值是负的可能经过dungeon[i][j]这个补血补的很多的房间后都能到达dp[i][j+1]或dp[i+1][j],根据题目的规定此时dp[i][j]应该等于1(最小的正整数),用方程表示即为dp[i][j] = max(1,dp[i][j+1]-dungeon[i][j]) 或 dp[i][j] = max(1,dp[i+1][j]-dungeon[i][j]),这两个方程代表两种选择,那选择的标准是什么呢?由题意只骑士的初始生命值要尽可能小,如此一来就能将这两个方程合并了:dp[i][j] = min( max(1,dp[i][j+1]-dungeon[i][j]),max(1,dp[i+1][j]-dungeon[i][j]) )。这样状态转移方程就求解出来了。
这道题如果换作是一个习惯使用倒序解法的解答者来解答可能会很快就能求解出来,像我这样习惯于正序解法的这个题上吃了很大的亏,这个题我做了很长时间,中途去睡了一觉(中午1点到下午4点半。。。),结果到了五点半才想到换解法。
下面上代码:
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
int mlen = dungeon.length;
if(mlen==0)return 1;
int nlen = dungeon[0].length;
int[][]opt = new int[mlen][nlen];
int[][]dp = new int[mlen][nlen];
for(int i = mlen-1;i>=0;i--) {
for(int j = nlen-1;j>=0;j--) {
if(i==mlen-1&&j==nlen-1)dp[i][j] = dungeon[i][j]>=0?1:(1-dungeon[i][j]);
else if(i==mlen-1) {
dp[i][j] = Math.max(dp[i][j+1]-dungeon[i][j],1);
}
else if(j==nlen-1) {
dp[i][j] = Math.max(dp[i+1][j]-dungeon[i][j],1);
}
else {
dp[i][j] = Math.min(Math.max(dp[i+1][j]-dungeon[i][j],1),Math.max(1,dp[i][j+1]-dungeon[i][j]));
}
}
}
/*
for(int i = 0;i<mlen;i++) {
for(int j = 0;j<nlen;j++)
System.out.print(dp[i][j]+" ");
System.out.println();
}
System.out.println();
for(int i = 0;i<mlen;i++) {
for(int j = 0;j<nlen;j++)
System.out.print(opt[i][j]+" ");
System.out.println();
}
*/
return dp[0][0];
}
}
这次题目的求解的确是个很艰辛的过程,吃一堑长一智吧!
动态规划——Dungeon Game的更多相关文章
- Dungeon Game ——动态规划
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. ...
- LeetCode之“动态规划”:Dungeon Game
题目链接 题目要求: The demons had captured the princess (P) and imprisoned her in the bottom-right corner of ...
- 174. Dungeon Game(动态规划)
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. ...
- [LeetCode] Dungeon Game 地牢游戏
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. ...
- 【leetcode】Dungeon Game
Dungeon Game The demons had captured the princess (P) and imprisoned her in the bottom-right corner ...
- 【leetcode】Dungeon Game (middle)
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. ...
- 动态规划Dynamic Programming
动态规划Dynamic Programming code教你做人:DP其实不算是一种算法,而是一种思想/思路,分阶段决策的思路 理解动态规划: 递归与动态规划的联系与区别 -> 记忆化搜索 -& ...
- 【LeetCode】174. Dungeon Game
Dungeon Game The demons had captured the princess (P) and imprisoned her in the bottom-right corner ...
- Dungeon Game (GRAPH - DP)
QUESTION The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a ...
随机推荐
- To making it count.
- How do you take your caviar, sir? 鱼子酱还要吗,先生? - No caviar for me, thanks. Never did like it much. ...
- 《Java》第五周学习总结20175301
https://gitee.com/ShengHuoZaiDaXue/20175301.git 本周我学习了第六章的内容接口 重要内容有 理解接口 接口参数 面向接口编程 abstract类与接口的比 ...
- Python——使用高德API获取POI(以深圳南山医疗保健服务POI为例)
以下内容为原创,转载请注明出处. import xlwt #创建Excel,见代码行8,9,11,25,28:CMD下:运行pip install xlwt进行安装 import urllib.req ...
- Linux系统GNOME主题安装与Tweaks工具使用
需要软件: GNOME Tweaks--使主题修改更加容易一个工具 安装主题: 下载主题:mac themes下载链接:https://www.gnome-look.org/p/1241688/ 这里 ...
- vagrant极简教程:快速搭建centos7
作为开发人员,只要你的应用最终是放在linux环境执行,那么最好就是将本地开发环境也线上一致.不管是用windows系统,还是mac系统,即使你本地程序跑得好好的,也经常会出现一上线就各种bug的现象 ...
- 二分查找算法的java实现
1.算法思想: 二分查找又称折半查找,它是一种效率较高的查找方法. 时间复杂度:O(nlogn) 二分算法步骤描述: ① 首先在有序序列中确定整个查找区间的中间位置 mid = ( low + ...
- dba_segements 没有所有的表的信息
这是oracle11g新增的功能,假设一个一般的用户user新建了一张表user_table,这时切换到sys用户查看dba_segments 查看user_table的信息,发现dba_segmen ...
- 2018-2019-2 20165231《网络对抗技术》Exp0 Kali安装 Week1
下载Kali Linux系统 进入官网进入下载页面,因为我们是在虚拟机内使用,而官网已经为我们提供了VM版的所以我就直接下载了这个版本的. 根据官网提示使用管理员帐号root(密码为toor)登录,创 ...
- MQTT报文格式
MQTT报文结构 控制报文由三部分组成: 1.Fixed header 固定报头,所有报文都包含 2.Variable header 可变报头,部分报文包含 3.Body 有效载荷,部分报文包含 固定 ...
- ollydbg入门记录
1.软件窗口说明 OllyDBG 中各个窗口的名称如下图.简单解释一下各个窗口的功能, 反汇编窗口:显示被调试程序的反汇编代码,标题栏上的地址.HEX 数据.反汇编.注释可以通过在窗口中右击出现的菜单 ...