这似乎是我在leetcode上做的第一道动态规划的题。这道题挺有意思的。下面给出题目地址:
这道题的意思是说给定一个m*n的数组,数组中的每一个位置代表地牢中的一个房间,里面的数代表里面的怪兽(负数)!血药(正数),和空房间(零)。王子要从(0,0)位置出发,只能往右或往下走,走到(m-1,n-1)位置,王子会有一个血量,血量小于等于零时王子死亡,现在要找出一条路径使王子不死且需要的初始生命值最小。简单来说,这题可以看作寻找一条只能往右或往下的路径,路径上每个位置的状态值等于经过的数之和,找出使最小状态值最大的路径。
这咋一看有点像一道图论题。像是在找最小带权路。但是这题却是一道动态规划题,但是状态的转移是由技巧的。如果从(0,0)往后进行动态规划,那么我们的目标是要求到终点的路径的最大最小状态值,所以要保存最小状态值。如果这样保存,我们很容易发现要递推下一个位置上的最大最小状态值不仅跟过去节点的最大最小状态值有关,也和当前生命值有关。如果只保存最大最小状态值,就是说到终点的使最小状态值最大的路径,不一定就是到结果路径上某个节点的最大最小状态值路径相同,即全局最优解不包含局部最优解。如果同时保存生命值,那无疑增加了非常多的计算量。
那么怎么办呢?我想到的方法是从(m-1,n-1)往回动归 。把这个节点要走到终点需要的最小生命值作为节点的状态值,有这个状态值递推下个节点的状态值(递推的时候要保证状态值大于1)。这样,如果找到一条路径从起点以最小生命值走到终点,那么在这个路径上的每一个节点都应该大于等于节点的最小生命值并可以走节点状态值最小的路径,使全局最优解包含局部最优解。
这里很容易想到的是递归,用一个数组保存节点是否访问过,不过这里可以用循环。把每一个从右上到左下的斜边看作一个阶段。下一个阶段的值由上一个阶段的值决定。就可以看作是多阶段的动态规划,可以用循环解决。此处用刷表法或填表法都可以。
以下是实现代码:
class Solution {
public:
vector<vector<int>> dp;
int calculateMinimumHP(vector<vector<int>>& dungeon) {
int i,j;
int M = dungeon.size();
int N = dungeon[0].size();
for(i=0;i<M;i++)
{
vector<int> temp(N);
dp.push_back(temp);
}
dp[M-1][N-1]=-dungeon[M-1][N-1]+1;
if(dp[M-1][N-1]<1)
dp[M-1][N-1]=1;
for(i=M+N-3;i>=0;i--)
{
for(j=max(0,i-N+1);j<=min(M-1,i);j++)
{
if(j+1<M)
{
dp[j][i-j]=dp[j+1][i-j]-dungeon[j][i-j];
if(i-j+1<N)
dp[j][i-j]=min(dp[j][i-j+1]-dungeon[j][i-j],dp[j][i-j]);
}
else if(i-j+1<N)
dp[j][i-j]=dp[j][i-j+1]-dungeon[j][i-j];
else
cout<<"aaaaaa"<<endl;
if(dp[j][i-j]<1)
dp[j][i-j]=1;
//cout<<j<<" "<<i-j<<" "<<dp[j][i-j]<<endl;
}
}
return dp[0][0];
}
};
结果: