AlgoMaster Logo

Dungeon Game

hardFrequencyUpdated September 21, 2026

Understanding the Problem

We need to find the minimum starting health for the knight so that he can travel from the top-left corner to the bottom-right corner, moving only right or down, and never have his health drop to 0 or below at any point along the path.

This differs from a standard minimum-path-sum problem. With path sum, only the total matters. Here, every intermediate state matters: the knight must survive at every step, not merely arrive at the end with positive health. A path that passes through a large health boost late in the journey is useless if the knight dies before reaching it.

The minimum health needed at any cell depends on what lies ahead rather than on what came before. If the path after a cell is damaging, the knight needs more health when entering it. If the path ahead is full of power-ups, he can enter with less. Because the requirement at each cell is determined by the future, the problem is solved by working backwards from the princess's cell.

Key Constraints:

  • 1 <= m, n <= 200 → The grid can have up to 40,000 cells, so an O(m*n) dynamic programming solution runs comfortably. Enumerating all paths is exponential and rules out brute force for large grids.
  • -1000 <= dungeon[i][j] <= 1000 → A path visits at most m + n - 1 = 399 cells, so any running sum stays within roughly ±400,000. 32-bit integers are enough.

Approach 1: Brute Force (DFS with All Paths)

Intuition

Enumerate every possible path from the top-left to the bottom-right. For each path, calculate the minimum starting health needed so the knight survives every room, then return the minimum across all paths.

For a single path, the minimum starting health is determined by the worst dip in the running sum of room values. If we start with health h and the running sum at some point reaches its lowest value minPrefix, then survival requires h + minPrefix >= 1, so h >= 1 - minPrefix.

Algorithm

  1. Use DFS to explore all paths from (0, 0) to (m-1, n-1), moving only right or down.
  2. At each cell, add the cell's value to the running sum. Track the minimum prefix sum seen so far along this path.
  3. When we reach (m-1, n-1), compute the starting health needed for this path: max(1, 1 - minPrefixSum).
  4. Return the minimum starting health across all paths.

Example Walkthrough

1Start DFS at (0,0). Explore all 6 paths to (2,2).
0
1
2
0
start
-2
-3
3
1
-5
-10
1
2
10
30
-5
1/8

Code

The DFS recomputes health requirements for the same suffixes across many overlapping paths. The next approach computes the minimum health needed at each cell exactly once and reuses it.

Approach 2: Reverse DP (2D Table)

Intuition

A forward DP, where dp[i][j] is computed from dp[i-1][j] and dp[i][j-1], works for Minimum Path Sum but breaks down here. At each cell, two quantities matter: the running sum so far (to know current health) and the minimum dip along the way (to know the starting health needed). These two quantities are at odds. A path with a better running sum might have had a worse dip earlier, and a path with a smaller dip might have a worse sum going forward. Neither value alone tells you which incoming path to keep, so the forward state cannot be reduced to a single number per cell.

Reversing direction fixes this. Process cells from bottom-right to top-left and define dp[i][j] as the minimum health the knight needs when entering cell (i, j) to survive the rest of the journey. This is a single well-defined number per cell because everything it depends on, the path ahead, has already been computed.

Algorithm

  1. Create a 2D DP table where dp[i][j] = minimum health needed when entering cell (i, j).
  2. Base case: At (m-1, n-1), the knight needs max(1, 1 - dungeon[m-1][n-1]) health.
  3. Last row and last column: Only one direction is available (right in the last row, down in the last column), so fill these directly from the single neighbor.
  4. General case: For cell (i, j), the knight will choose the direction that requires less health. So dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j]).
  5. Return dp[0][0].

Example Walkthrough

1Initial dungeon grid. Start filling DP from bottom-right.
0
1
2
0
-2
-3
3
1
-5
-10
1
2
10
30
start here
-5
1/8

Code

The time complexity is already optimal, but the table uses O(m * n) extra space. Each row of the DP table depends only on the row below it, so a single 1D array is enough.

Approach 3: Space-Optimized DP

Intuition

Looking at the recurrence, cell (i, j) depends only on (i+1, j) and (i, j+1). This means we only need the current row and the row below. We can go a step further and use a single 1D array. Process columns from right to left within each row. When we compute dp[j], the value dp[j] currently holds is from row i+1 (not yet overwritten), and dp[j+1] is from row i (already updated for the current row). So both dependencies are available.

Algorithm

  1. Create a 1D array dp of size n.
  2. Process rows from bottom (m-1) to top (0). Within each row, process columns from right (n-1) to left (0).
  3. For the bottom-right cell: dp[n-1] = max(1, 1 - dungeon[m-1][n-1]).
  4. For other cells in the last row: dp[j] = max(1, dp[j+1] - dungeon[m-1][j]).
  5. For other rows: at each cell (i, j), pick min(dp[j], dp[j+1]) and compute dp[j] = max(1, min - dungeon[i][j]).
  6. Return dp[0].

Example Walkthrough

1Initialize dp array. Process row 2 (bottom row) right to left.
0
0
1
0
2
0
1/8

Code