THN Interview Prep

174. Dungeon Game

At a Glance

Problem (one-liner)

Grid of rooms with gains/losses (dungeon[row][col]). Start top-left, move only right or down; minimum HP must stay strictly positive at every step. Return the minimum initial HP to reach bottom-right alive.

Recognition Cues

  • Minimum starting resource so path never drops below 1
  • Future depends on later rooms — reverse DP from goal (like “min cost from here to exit”)
  • Unlike max-sum path: need minimax survival, not max loot

Diagram

At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.

Loading diagram…

Approaches

  • Brute all paths — exponential.
  • Reverse DPneed[row][col] = minimum HP entering (row,col) to reach princess — O(mn) time, O(n) space possible — optimal.

Optimal Solution

Go

func calculateMinimumHP(dungeon [][]int) int {
	rowCount := len(dungeon)
	colCount := len(dungeon[0])
	// need[row][col] = min HP when ENTERING this cell to survive to princess
	need := make([][]int, rowCount)
	for row := range need {
		need[row] = make([]int, colCount)
	}
	for row := rowCount - 1; row >= 0; row-- {
		for col := colCount - 1; col >= 0; col-- {
			if row == rowCount-1 && col == colCount-1 {
				need[row][col] = max(1, 1-dungeon[row][col])
				continue
			}
			var bestNext int
			if row == rowCount-1 {
				bestNext = need[row][col+1]
			} else if col == colCount-1 {
				bestNext = need[row+1][col]
			} else {
				bestNext = min(need[row+1][col], need[row][col+1])
			}
			need[row][col] = max(1, bestNext-dungeon[row][col])
		}
	}
	return need[0][0]
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

JavaScript

function calculateMinimumHP(dungeon) {
  const rowCount = dungeon.length;
  const colCount = dungeon[0].length;
  const need = Array.from({ length: rowCount }, () =>
    new Array(colCount).fill(0)
  );
  for (let row = rowCount - 1; row >= 0; row -= 1) {
    for (let col = colCount - 1; col >= 0; col -= 1) {
      if (row === rowCount - 1 && col === colCount - 1) {
        need[row][col] = Math.max(1, 1 - dungeon[row][col]);
        continue;
      }
      let bestNext;
      if (row === rowCount - 1) {
        bestNext = need[row][col + 1];
      } else if (col === colCount - 1) {
        bestNext = need[row + 1][col];
      } else {
        bestNext = Math.min(need[row + 1][col], need[row][col + 1]);
      }
      need[row][col] = Math.max(1, bestNext - dungeon[row][col]);
    }
  }
  return need[0][0];
}

Walkthrough

Bottom-right needs enough HP after accounting for final cell. Walk backward: before entering a cell, you must cover loss dungeon[row][col] and still have enough for the cheaper optimal next step.

Invariant: need[row][col] is the minimum HP at entry to (row,col) to eventually reach the princess with HP always ≥ 1.

Edge Cases

  • Single cell — max(1, 1 - dungeon[0][0])
  • All positive rooms — still at least 1 HP everywhere
  • Heavy negatives on every step — answer grows

Pitfalls

  • Forward DP on max gain — wrong objective (survival vs sum)
  • Off-by-one on “strictly positive” — clamp with max(1, ...)

Similar Problems

Variants

  • Four directions allowed — need SPFA or Dijkstra-style on states (row,col,hp) or BFS levels
  • Maximize loot with survival — lexicographic or constrained optimization
  • Multiple princesses / exits — min over targets in reverse multi-target DP

Mind-Map Tags

#grid-dp #reverse-dp #min-initial-resource #survival #clamp-positive

Last updated on

Spotted something unclear or wrong on this page?

On this page