[LeetCode#174] Dungeon Game

Problem:

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.

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.

-2 (K) -3 3
-5 -10 1
10 30 -5 (P)

 

Notes:

  • The knight's health has no upper bound.
  • Any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned. 

 

Credits:
Special thanks to @stellari for adding this problem and creating all test cases.

Wrong solution:

public class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        if (dungeon == null)
            throw new IllegalArgumentException("dungeon is null");
        if (dungeon.length == 0 || dungeon[0].length == 0)
            return 1;
        int m = dungeon.length, n = dungeon[0].length;
        int[] minHP = new int[n];
        int[] remainHP = new int[n];
        minHP[0] = (dungeon[0][0] > 0 ? 0 : 1 - dungeon[0][0]);
        remainHP[0] = (dungeon[0][0] > 0 ? dungeon[0][0] : 1);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int left = Integer.MAX_VALUE, left_remain = 0, upper = Integer.MAX_VALUE, upper_remain = 0;
                if (i == 0 && j == 0)
                    continue; 
                if (i != 0) {
                    if (remainHP[j] + dungeon[i][j] > 0) {
                        left = minHP[j];
                        left_remain = remainHP[j] + dungeon[i][j];
                    } else{
                        left = 1 - minHP[j] - dungeon[i][j];
                        left_remain = 1;
                    }
                }
                if (j != 0) {
                    if (remainHP[j-1] + dungeon[i][j] > 0) {
                        upper = minHP[j-1];
                        upper_remain = remainHP[j-1] + dungeon[i][j];
                    } else{
                        upper = 1 - minHP[j-1] - dungeon[i][j];
                        upper_remain = 1;
                    }
                }
                if (left < upper) {
                    minHP[j] = left;
                    remainHP[j] = left_remain;
                } else{
                    minHP[j] = upper;
                    remainHP[j] = upper_remain;
                }
            }
        }
        return minHP[n-1];
    }
}

Analysis:

The above solution is wrong, and really complex. It caculates the minimum HP from top to the bottom, which actually it is quite different from the problem ask us to do.
Coflict: we want to know the minimum HP we should have at dungeon[0][0], and the HP would be lost or added at the dungeons afterward. Until we really reach those grids, we would never know the minimum HP prvious grids should have. Is that contradict our philoshpy of using DP? accumulate the information we need during the dp process. 

Actually the computing direction of dp method is very very important! The question is to compute the minimum HP we should have at dungeon[0][0]. And usually we should get this information at the last step of a dp process. Thus it strongly hints us we should design a dp process to compute from the bottom to the top. 

Brief Picture:
Using a min[][] matrix, min[i][j] means the minimum HP we should have at dungeon[i][j] to finally save the queen. 
Note: min[i][j] means the minimum HP we should have when we enter dugeon[i][j].

Iff we have already computed the minimum HP at dugeon[i][j], we could use it to compute the left dugeon and upper dugeon's minimum HP.
(iff we enter dugeon[i, j] from left dugeon[i, j-1])
min[i][j-1] + dugeon[i][j-1] = min[i][j]
Note: dugeon[i][j-1] could be benefits or harms, harm is in negative number, and min represents the minimum hp. thus we should use "min[i][j-1] + dugeon[i][j-1] = min[i][j]" rather than "min[i][j-1] - dugeon[i][j-1] = min[i][j]". You should not be affected by dugeon's name meaning.
===>
min[i][j-1] = min[i][j] - dugeon[i][j-1]
Note: we should guarantee min[i][j-1] larger than 1, which would in turn post the requriments over the dugeon at min[i][j-1]'s left and upper side. 
int right = Math.max(min[i][j+1] - dungeon[i][j], 1);
This can guaratee the hp would never drop to 0 or below 0.
a. when we enter dugeon[i][j], the king's hp must not smaller than 1.
Math.max(min[i][j+1] - dungeon[i][j], 1)
b. when we leave dugeon, the hp must not less than 1. since we compute from bottom to top, at dugeons below or after dugeon[i][j], we restrict them no smaller than 1. thus "min[i][j+1] - dungeon[i][j]" guarantee it would never drop below one, which 
b.1. iff dungeon[i][j] is a nagitve number, we can guarantee we at the state of min[i][j+1] when we exit. 
min[i][j] + dugeon[i][j] = min[i][j+1] 


One thing we are pretty sure is that the minimum HP we should have at last dugeon. 
min[m-1][n-1] = Math.max(1 - dungeon[m-1][n-1], 1);
which means after we include the dungeon[m-1][n-1]'s benefit or harm, we should still have 1 HP left. 


It is really not hard!!! Right!!!
But it could efficiently sovle such seemly complex problem, right?

Solution:

public class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        if (dungeon.length == 0 || dungeon[0].length == 0)
            return 1;
        int m = dungeon.length;
        int n = dungeon[0].length;
        int[][] min = new int[m][n];
        min[m-1][n-1] = Math.max(1 - dungeon[m-1][n-1], 1);
        
        for (int i = m - 2; i >= 0; i--) {
            min[i][n-1] = Math.max(min[i+1][n-1] - dungeon[i][n-1], 1);
        }
        for (int j = n - 2; j >= 0; j--) {
            min[m-1][j] = Math.max(min[m-1][j+1] - dungeon[m-1][j], 1);
        }
        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                int right = Math.max(min[i][j+1] - dungeon[i][j], 1);
                int down =  Math.max(min[i+1][j] - dungeon[i][j], 1);
                min[i][j] = Math.min(right, down);
            }            
        }
        return min[0][0];
    }
}

你可能感兴趣的:([LeetCode#174] Dungeon Game)