Dynamic Programming
Dynamic programming solves problems by breaking them into overlapping subproblems, solving each once, and storing the result to avoid redundant computation.
Every DP solution follows four steps:
- Define what
dp[i](ordp[i][j]) represents - Recurrence relation — how a cell depends on previously computed cells
- Base cases — smallest subproblems you can answer directly
- Fill order — ensure dependencies are filled before they are needed
Example 1: Fibonacci
function fib($num) {
if ($num < 0) {
return 0;
}
// $dp[$i] means the i-th fibonacci number
$dp = [];
// $dp[0] and $dp[1] are base cases
$dp[0] = 0;
$dp[1] = 1;
// fill order = left to right
// recurrence relation: $dp[$i] = $dp[$i-1] + $dp[$i-2]
for ($i=2; $i<=$num; $i++) {
$dp[$i] = $dp[$i-1] + $dp[$i-2];
}
return $dp[$num];
}
| Step | Definition |
|---|---|
dp[i] means |
the i-th Fibonacci number |
| Base cases | dp[0] = 0, dp[1] = 1 |
| Recurrence | dp[i] = dp[i-1] + dp[i-2] |
| Fill order | left to right |
Walkthrough: fib(6)
Initial — base cases filled:
block-beta
columns 7
b0["dp[0]=0"] b1["dp[1]=1"] b2["dp[2]=?"] b3["dp[3]=?"] b4["dp[4]=?"] b5["dp[5]=?"] b6["dp[6]=?"]
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef unknown fill:#eee,stroke:#aaa,color:#999
class b0,b1 base
class b2,b3,b4,b5,b6 unknown
After filling:
block-beta
columns 7
b0["dp[0]=0"] b1["dp[1]=1"] b2["dp[2]=1"] b3["dp[3]=2"] b4["dp[4]=3"] b5["dp[5]=5"] b6["dp[6]=8"]
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef filled fill:#90EE90,stroke:#555,color:#000
classDef answer fill:#FFD700,stroke:#555,color:#000
class b0,b1 base
class b2,b3,b4,b5 filled
class b6 answer
dp[2] = dp[1] + dp[0] = 1 + 0 = 1
dp[3] = dp[2] + dp[1] = 1 + 1 = 2
dp[4] = dp[3] + dp[2] = 2 + 1 = 3
dp[5] = dp[4] + dp[3] = 3 + 2 = 5
dp[6] = dp[5] + dp[4] = 5 + 3 = 8
Answer: fib(6) = 8
Example 2: Unique Paths
A robot starts at the top-left of an m x n grid and wants to reach the bottom-right. It can only move right or down. How many unique paths are there?
function uniquePaths($rows, $cols) {
if ($rows <= 0 || $cols <= 0) {
return 0;
}
// $dp[$i][$j] means the number of unique paths to reach cell (i, j)
$dp = [];
for ($i=0; $i<$rows; $i++) {
$dp[$i] = [];
for ($j=0; $j<$cols; $j++) {
$dp[$i][$j] = -1;
}
}
// base cases:
// $dp[$i][0] is 1 because there is only one way to go from top-left to bottom-left
// $dp[0][$j] is 1 because there is only one way to go from top-left to top-right
for ($i=0; $i<$rows; $i++) {
$dp[$i][0] = 1;
}
for ($j=0; $j<$cols; $j++) {
$dp[0][$j] = 1;
}
// fill order = left to right, top to bottom:
// dp[i][j] always needs north and west, both already filled.
// recurrence relation: $dp[$i][$j] = $dp[$i-1][$j] + $dp[$i][$j-1]
for ($i=1; $i<$rows; $i++) {
for ($j=1; $j<$cols; $j++) {
$dp[$i][$j] = $dp[$i-1][$j] + $dp[$i][$j-1];
}
}
return $dp[$rows-1][$cols-1];
}
| Step | Definition |
|---|---|
dp[i][j] means |
number of unique paths to reach cell (i, j) |
| Base cases | dp[0][j] = 1 (top row), dp[i][0] = 1 (left column) |
| Recurrence | dp[i][j] = dp[i-1][j] + dp[i][j-1] |
| Fill order | left to right, top to bottom |
To reach (i, j), the robot must have come from above (i-1, j) or from the left (i, j-1).
Walkthrough: 3x3 grid
Initial — base cases filled:
block-beta
columns 3
b00["1"] b01["1"] b02["1"]
b10["1"] b11["?"] b12["?"]
b20["1"] b21["?"] b22["?"]
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef unknown fill:#eee,stroke:#aaa,color:#999
class b00,b01,b02,b10,b20 base
class b11,b12,b21,b22 unknown
After filling:
block-beta
columns 3
b00["1"] b01["1"] b02["1"]
b10["1"] b11["2"] b12["3"]
b20["1"] b21["3"] b22["6"]
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef filled fill:#90EE90,stroke:#555,color:#000
classDef answer fill:#FFD700,stroke:#555,color:#000
class b00,b01,b02,b10,b20 base
class b11,b12,b21 filled
class b22 answer
dp[1][1] = dp[0][1] + dp[1][0] = 1 + 1 = 2
dp[1][2] = dp[0][2] + dp[1][1] = 1 + 2 = 3
dp[2][1] = dp[1][1] + dp[2][0] = 2 + 1 = 3
dp[2][2] = dp[1][2] + dp[2][1] = 3 + 3 = 6
Answer: 6 unique paths
Example 3: Regular Expression Matching
Given string s and pattern p with . (matches any single character) and * (zero or more of the preceding character), return whether s fully matches p.
function regEx($string, $pattern) {
// $dp[$s][$p] means whether substr($string, 0, $s) matches substr($pattern, 0, $p)
$dp = [];
for ($s=0; $s<=strlen($string); $s++) {
$dp[$s] = [];
for ($p=0; $p<=strlen($pattern); $p++) {
$dp[$s][$p] = false;
}
}
// base cases:
// $dp[0][0] = true because empty string matches empty pattern
// $dp[0][1] = false because empty string does not match a single letter or .
// $dp[$s][0] = false for $s != 0 because non-empty string cannot match empty pattern
$dp[0][0] = true;
// fill order = left to right, top to bottom:
// recurrence relation: $dp[$s][$p] =
// if $pattern[$p-1] is letter:
// if $pattern[$p-1] == $string[$s-1]: then $dp[$s-1][$p-1]
// if $pattern[$p-1] != $string[$s-1]: then false
// if $pattern[$p-1] is .:
// then $dp[$s-1][$p-1]
// if $pattern[$p-1] is *:
// zero occurrences: if $dp[$s][$p-2] is true → true
// one or more: if $dp[$s-1][$p] is true and preceding pattern char matches → true
for ($s=0; $s<=strlen($string); $s++) {
for ($p=0; $p<=strlen($pattern); $p++) {
if ($s == 0 && $p == 0) continue;
if ($p == 0) continue;
if ($pattern[$p-1] != "." && $pattern[$p-1] != "*") { // letter
if ($s == 0) continue;
if ($pattern[$p-1] == $string[$s-1]) {
$dp[$s][$p] = $dp[$s-1][$p-1];
}
continue;
}
if ($pattern[$p-1] == ".") {
if ($s == 0) continue;
$dp[$s][$p] = $dp[$s-1][$p-1];
continue;
}
if ($pattern[$p-1] == "*") {
// zero occurrences
if ($dp[$s][$p-2]) {
$dp[$s][$p] = true;
continue;
}
// one or more occurrences
if ($s > 0 && ($pattern[$p-2] == $string[$s-1] || $pattern[$p-2] == '.') && $dp[$s-1][$p]) {
$dp[$s][$p] = true;
}
}
}
}
return $dp[strlen($string)][strlen($pattern)];
}
| Step | Definition |
|---|---|
dp[s][p] means |
whether s[:s] matches p[:p] |
| Base cases | dp[0][0] = true; dp[0][p] = dp[0][p-2] if pattern[p-1]='*' |
| Recurrence | depends on current pattern character (letter, ., or *) |
| Fill order | left to right, top to bottom |
Walkthrough: s = "aab", p = "a*b"
s: a(0) a(1) b(2)
p: a(0) *(1) b(2)
Initial — base cases filled:
block-beta
columns 5
h0[" "] h1["ε"] h2["a"] h3["*"] h4["b"]
r0["ε"] d00["T"] d01["F"] d02["T"] d03["F"]
r1["a"] d10["F"] d11["?"] d12["?"] d13["?"]
r2["a"] d20["F"] d21["?"] d22["?"] d23["?"]
r3["b"] d30["F"] d31["?"] d32["?"] d33["?"]
classDef header fill:#ddd,stroke:#555,color:#000
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef unknown fill:#eee,stroke:#aaa,color:#999
class h0,h1,h2,h3,h4,r0,r1,r2,r3 header
class d00,d02,d10,d20,d30 base
class d01,d03 base
class d11,d12,d13,d21,d22,d23,d31,d32,d33 unknown
After filling:
block-beta
columns 5
h0[" "] h1["ε"] h2["a"] h3["*"] h4["b"]
r0["ε"] d00["T"] d01["F"] d02["T"] d03["F"]
r1["a"] d10["F"] d11["T"] d12["T"] d13["F"]
r2["a"] d20["F"] d21["F"] d22["T"] d23["F"]
r3["b"] d30["F"] d31["F"] d32["F"] d33["T"]
classDef header fill:#ddd,stroke:#555,color:#000
classDef base fill:#ADD8E6,stroke:#555,color:#000
classDef t fill:#90EE90,stroke:#555,color:#000
classDef answer fill:#FFD700,stroke:#555,color:#000
class h0,h1,h2,h3,h4,r0,r1,r2,r3 header
class d00,d02,d10,d20,d30 base
class d11,d12,d22 t
class d33 answer
Key cells explained:
| Cell | Pattern char | Rule | Depends on | Result |
|---|---|---|---|---|
dp[0][2] |
* |
zero occurrences: dp[0][0]=T |
dp[0][0] |
T |
dp[1][1] |
a |
a==a, diagonal |
dp[0][0]=T |
T |
dp[1][2] |
* |
one or more: a==a, north |
dp[0][2]=T |
T |
dp[2][2] |
* |
one or more: a==a, north |
dp[1][2]=T |
T |
dp[3][3] |
b |
b==b, diagonal |
dp[2][2]=T |
T |
Answer: true — "aab" matches "a*b"