{x}
blog image

Dungeon Game

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

Solution Explanation: Dungeon Game

This problem asks for the minimum initial health required for a knight to traverse a dungeon from the top-left to the bottom-right, collecting health boosts and enduring health reductions along the way. The knight can only move right or down. The solution uses dynamic programming.

Approach:

We use a dynamic programming approach to solve this problem efficiently. The key idea is to build a DP table where dp[i][j] represents the minimum health the knight needs at cell (i, j) to guarantee survival to the end. We work backwards from the princess's location (bottom-right) to the starting location (top-left).

DP Relation:

To reach a cell (i, j), the knight must have come from either cell (i+1, j) (down) or (i, j+1) (right). The minimum health needed at (i, j) is determined by the minimum of the health needed at these two neighboring cells, considering the health change at (i, j):

  • minHealthNeededAt(i, j) = min(healthNeededAt(i+1, j), healthNeededAt(i, j+1))

However, we need to account for the fact that the health at cell (i, j) can either increase or decrease. We need to ensure the knight doesn't have health less than or equal to 0 at any point. This leads to the final DP relation:

dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])

The max(1, ...) ensures the knight always has at least 1 health point.

Base Cases:

  • The base cases are the cells at the bottom-right edge. These will have a minimum health requirement of 1 (to survive entering that final cell, regardless of the value in the cell).

Algorithm:

  1. Initialization: Create a DP table dp with dimensions one larger than the dungeon in both directions. Initialize all cells to a large value (e.g., infinity) except for the base cases (bottom-right and the cells on the rightmost and bottommost columns), which are set to 1.

  2. Iteration: Iterate through the DP table from the bottom-right to the top-left, using the DP relation to calculate the minimum health needed at each cell.

  3. Result: The minimum initial health required is stored in dp[0][0].

Time and Space Complexity:

  • Time Complexity: O(m*n), where 'm' and 'n' are the dimensions of the dungeon. We iterate through the DP table once.
  • Space Complexity: O(m*n) to store the DP table. We can potentially reduce this to O(min(m,n)) if we only store one row or column at a time.

Code Examples (with explanations incorporated into comments):

(Python)

import sys
 
def calculateMinimumHP(dungeon):
    m, n = len(dungeon), len(dungeon[0])
    # DP table: Initialize with a large value (infinity)
    dp = [[sys.maxsize] * (n + 1) for _ in range(m + 1)]
    # Base cases: Bottom-right and last rows/columns need at least 1 health
    dp[m][n - 1] = dp[m - 1][n] = 1
    for i in range(m - 1, -1, -1):
        for j in range(n - 1, -1, -1):
            # DP relation: Minimum health needed = max(1, min(health from below, health from right) - cell value)
            dp[i][j] = max(1, min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j])
    # Result is at the top-left cell
    return dp[0][0]
 

Other languages (Java, C++, Go, C#) follow similar logic with appropriate syntax changes. The comments in the Python code above highlight the key steps of the algorithm and explain the DP relation. The other language versions are structurally very similar.