Problem Statement
Dungeon Game
A knight starts at the top-left corner of a grid and wants to reach the bottom-right corner. He can only move right or down. Each cell either heals him (a positive number) or hurts him (a negative number). His health changes as he walks, and if it ever drops to 0 or below, he dies. The question is: what is the smallest amount of health he can start with and still survive the whole trip? Here is the trick that makes this solvable: we work BACKWARDS, from the bottom-right cell back to the start. We build a table called dp, where dp[i][j] means "the smallest amount of health the knight needs to have the moment he steps onto cell (i,j), so that he can finish the rest of the path alive." Working backwards sounds odd, so here is the real-world picture. Imagine planning a road trip where you must arrive with at least a quarter tank of gas. You cannot know how much gas to start with until you first figure out how much each later leg of the trip burns. So you plan the last leg first, then the one before it, and so on, back to the start. That is exactly what we do here with health instead of gas.
Signals to notice
Brute force first
Try all paths — O(2^(m+n)).
The key insight
DP from bottom-right to top-left. dp[i][j] = max(1, min(right,down) - dungeon[i][j]). O(mn).
Trace it on dungeon=[[-2,-3,3],[-5,-10,1],[10,30,-5]]
Fill bottom-right first: dp[2][2]=max(1,1-(-5))=6 Last row (right-to-left): dp[2][1]=max(1,6-30)=1; dp[2][0]=max(1,1-10)=1 Last col (bottom-up): dp[1][2]=max(1,6-1)=5; dp[0][2]=max(1,5-3)=2 dp[1][1]=max(1,min(dp[2][1]=1,dp[1][2]=5)-(-10))=max(1,11)=11 dp[1][0]=max(1,min(dp[2][0]=1,dp[1][1]=11)-(-5))=max(1,6)=6 dp[0][1]=max(1,min(dp[1][1]=11,dp[0][2]=2)-(-3))=max(1,5)=5 dp[0][0]=max(1,min(dp[1][0]=6,dp[0][1]=5)-(-2))=max(1,7)=7 return dp[0][0]=7
What must stay true
Working backward: each cell knows minimum health needed to survive from there to the end. Clamped to ≥ 1.
Shape of the loop
for i from m-1 down to 0:
for j from n-1 down to 0:
need = (last cell) ? 1 : min(dp[i+1][j], dp[i][j+1]) # missing neighbor = +inf
dp[i][j] = max(1, need - dungeon[i][j])
return dp[0][0]Pseudocode only — the full worked solution lives in the Solution tab.
Easy way to go wrong
Working forward — requires tracking both current and minimum health. Backward only needs one value.