Dungeon Game

HARD

Description

The demons had captured the princess 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 was initially positioned in the top-left room and must fight his way through 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 (represented by negative integers), so the knight loses health upon entering these rooms; other rooms are either empty (represented as 0) or contain magic orbs that increase the knight's health (represented by positive integers).

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

Return the knight's minimum initial health so that he can rescue the princess.

Note that 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.

 

Example 1:

Input: dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
Output: 7
Explanation: The initial health of the knight must be at least 7 if he follows the optimal path: RIGHT-> RIGHT -> DOWN -> DOWN.

Example 2:

Input: dungeon = [[0]]
Output: 1

 

Constraints:

  • m == dungeon.length
  • n == dungeon[i].length
  • 1 <= m, n <= 200
  • -1000 <= dungeon[i][j] <= 1000

Approaches

Checkout 4 different approaches to solve Dungeon Game. Click on different approaches to view the approach and algorithm in detail.

Brute-Force Recursion

This approach uses simple recursion to explore all possible paths from the knight's starting position to the princess. The core idea is to work backward from the destination. We define a function that calculates the minimum health required at a cell (i, j) to guarantee survival until the end. This function recursively calls itself for the next possible cells (down and right) and chooses the path that requires less health.

Algorithm

  1. Define a recursive function findMinHealth(row, col) that returns the minimum health needed upon entering cell (row, col).
  2. Base Case: If the cell is the princess's room (m-1, n-1), the health h needed must satisfy h + dungeon[m-1][n-1] >= 1. So, the required health is max(1, 1 - dungeon[m-1][n-1]).
  3. Boundary Conditions: If row or col goes out of the grid, it's an invalid path. Return a very large value (e.g., Integer.MAX_VALUE) to ensure this path is not chosen.
  4. Recursive Step: For any other cell (row, col), the knight can move down or right. The minimum health required for the rest of the journey is the minimum of the health needed for the path starting from (row+1, col) and the path from (row, col+1).
    • minHealthForFuture = min(findMinHealth(row+1, col), findMinHealth(row, col+1))
    • The health h at (row, col) must satisfy h + dungeon[row][col] >= minHealthForFuture. Therefore, the health needed is max(1, minHealthForFuture - dungeon[row][col]).
  5. The initial call is findMinHealth(0, 0).

We define a recursive function, say findMinHealth(row, col), which calculates the minimum health required upon entering the cell (row, col) to be able to reach the princess. The function works as follows:

  • The base case is the princess's room at (m-1, n-1). To survive this room, the knight's health h must satisfy h + dungeon[m-1][n-1] >= 1. Since health must always be at least 1, the minimum health required upon entering this cell is max(1, 1 - dungeon[m-1][n-1]).

  • For any other cell (row, col), the knight can move down to (row+1, col) or right to (row, col+1). He will logically choose the path that requires a lower initial health. The minimum health needed for the subsequent journey is min(findMinHealth(row+1, col), findMinHealth(row, col+1)). Let's call this minHealthForFuture.

  • The health h upon entering (row, col) must be sufficient to survive the current room and the rest of the journey. This means h + dungeon[row][col] >= minHealthForFuture. Thus, the health needed at (row, col) is max(1, minHealthForFuture - dungeon[row][col]).

This recursive structure explores all possibilities, but since findMinHealth for the same cell is called multiple times through different paths, it leads to an exponential number of computations.

class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        // This will likely time out for larger inputs.
        return findMinHealth(dungeon, 0, 0);
    }

    private int findMinHealth(int[][] dungeon, int row, int col) {
        int m = dungeon.length;
        int n = dungeon[0].length;

        if (row >= m || col >= n) {
            return Integer.MAX_VALUE;
        }

        if (row == m - 1 && col == n - 1) {
            return Math.max(1, 1 - dungeon[row][col]);
        }

        int healthFromDown = findMinHealth(dungeon, row + 1, col);
        int healthFromRight = findMinHealth(dungeon, row, col + 1);

        int minHealthForFuture = Math.min(healthFromDown, healthFromRight);
        
        int neededHealth = minHealthForFuture - dungeon[row][col];

        return Math.max(1, neededHealth);
    }
}

Complexity Analysis

Time Complexity: O(2^(m+n))Space Complexity: O(m + n)

Pros and Cons

Pros:
  • Conceptually simple and a direct translation of the problem's recursive nature.
Cons:
  • Extremely inefficient due to re-computation of the same subproblems.
  • Will result in a 'Time Limit Exceeded' error for all but the smallest of grids.

Code Solutions

Checking out 4 solutions in different languages for Dungeon Game. Click on different languages to view the code.

public class Solution { public int CalculateMinimumHP ( int [][] dungeon ) { int m = dungeon . Length , n = dungeon [ 0 ]. Length ; int [][] dp = new int [ m + 1 ][]; for ( int i = 0 ; i < m + 1 ; ++ i ) { dp [ i ] = new int [ n + 1 ]; Array . Fill ( dp [ i ], 1 << 30 ); } dp [ m ][ n - 1 ] = dp [ m - 1 ][ n ] = 1 ; for ( int i = m - 1 ; i >= 0 ; -- i ) { for ( int j = n - 1 ; j >= 0 ; -- j ) { dp [ i ][ j ] = Math . Max ( 1 , Math . Min ( dp [ i + 1 ][ j ], dp [ i ][ j + 1 ]) - dungeon [ i ][ j ]); } } return dp [ 0 ][ 0 ]; } }

Video Solution

Watch the video walkthrough for Dungeon Game



Patterns:

Dynamic Programming

Data Structures:

ArrayMatrix

Companies:

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.