1. 程式人生 > >【LeetCode 174】Dungeon Game

【LeetCode 174】Dungeon Game

The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. 
The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned 
in the top-left room and must fight his way through the dungeon to rescue the princess.

The knight has an initial health point represented by a positive integer. If at any point his health point 
drops to 0 or below, he dies immediately.

Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering 
these rooms; other rooms are either empty (0's) or contain magic orbs that increase the knight's health 
(positive integers).

In order to reach the princess as quickly as possible, the knight decides to move only rightward or 
downward in each step.

Write a function to determine the knight's minimum initial health so that he is able to rescue the 
princess.

題意:給出一個M*N的迷宮,玩家從左上角K開始尋找路徑走到右下角P,只能向右或向下走。
玩家有個初始血量,經過一格時血量會與格子中的數字相加(負數也就意味著血量減少)。在遊戲
的任意時刻,只要玩家血量小於或等於0則失敗。求玩家要想成功走到右下角所需要的最少初始
血量。

For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path RIGHT-> RIGHT -> DOWN -> DOWN.

思路:根據題目型別,應該馬上想到動態規劃。類似的題目有很多,比如尋找從左上角到右下角的最小代價路徑、路徑總數量等,但本題與這些題有略微的不同。在計算最小代價路徑時,從左上到右下計算dp矩陣即可。但本題得從右下逆向往左上計算。

假如從左上往右下dp,每一格 dp[i][j]=min(dp[i-1][j],dp[i][j-1]) 代表到達這一格的最少血量消耗,但這樣只能計算出最後到達右下角的最小血量消耗,忽略了一個條件:到任何一格時玩家的血量都必須是正數才行。如果本題改成血量可以降到0及0以下,求到達右下角的最多剩餘血量,那麼就是傳統的最小代價路徑求解問題。但血量為正的條件使得這個最小代價路徑的途中玩家可能會因血量太低而死亡。

所以改成從右下往左上dp,每一格 dp[i][j] 代表踏入這一格需要的最少血量。在走完最後一個房間的時候血量至少要剩下1,因此最後的狀態可以當成是初始狀態,由後往前依次決定在每一個位置至少要有多少血量, 這樣一個位置的狀態是由其下面一個和和左邊一個的較小狀態決定的。

int calculateMinimumHP(vector<vector<int>>& dungeon) {
	int ROW = dungeon.size(), COL = dungeon[0].size();
	//多一列只是為了放初始值1
	vector<vector<int>> dp(ROW, vector<int>(COL + 1));
	dp[ROW - 1][COL] = 1;
	//初始化最下行
	for (int c = COL - 1; c >= 0; c--) {
		dp[ROW - 1][c] = max(dp[ROW - 1][c + 1] - dungeon[ROW - 1][c], 1);
	}
	//初始化最右列
	for (int r = ROW - 2; r >= 0; r--) {
		dp[r][COL - 1] = max(dp[r + 1][COL - 1] - dungeon[r][COL - 1], 1);
	}
	//dp遞推式
	for (int r = ROW - 2; r >= 0; r--) {
		for (int c = COL - 2; c >= 0; c--) {
			int curHealth = min(dp[r][c + 1], dp[r + 1][c]);
			dp[r][c] = max(curHealth - dungeon[r][c], 1);
		}
	}
	return dp[0][0];
}