Add dynamic-programming module: ten classic DP problems, memoization vs tabulation vs space-optimised, call-count and pitfall transcripts (11 tests)

This commit is contained in:
2026-11-09 08:41:01 +00:00
parent 911aef5515
commit cb1b28682f
33 changed files with 1551 additions and 0 deletions
@@ -0,0 +1,12 @@
Fibonacci: how many calls does each approach make?
==================================================
n fib(n) naive calls memo calls table steps
10 55 177 19 9
20 6765 21891 39 19
30 832040 2692537 59 29
40 102334155 331160281 79 39
naive calls = every call the recursion made (counted, not predicted).
memo calls = the same recursion with a notebook: 2n-1 calls, because each F(k) is computed once.
table steps = additions in the bottom-up loop (n-1).
@@ -0,0 +1,16 @@
Fibonacci: three things that go wrong
=====================================
1) HashMap.computeIfAbsent as the memo, recursive mapping function, n = 10
-> java.util.ConcurrentModificationException
same idea written as get / compute / put -> 55
2) The answer outgrows a long
fib(92) = 7540113804746346429
fib(93) -> ArithmeticException: long overflow (Math.addExact refuses to wrap around)
plain + instead of addExact would silently return -6246583658587674878
3) Recursion depth, House Robber with every house worth 1, in a thread with a 256 KB stack
memo n = 500 -> 250
memo n = 50,000 -> java.lang.StackOverflowError
tabulation n = 50,000 -> 25000
@@ -0,0 +1,14 @@
House Robber: money = [2, 7, 9, 3, 1]
=====================================
dp[k] = best total using only the first k houses
k [0, 1, 2, 3, 4, 5]
dp[k] [0, 2, 7, 11, 11, 12]
k=1 house worth 2: skip = dp[0] = 0, take = dp[0] + 2 = 2 -> dp[1] = 2
k=2 house worth 7: skip = dp[1] = 2, take = dp[0] + 7 = 7 -> dp[2] = 7
k=3 house worth 9: skip = dp[2] = 7, take = dp[1] + 9 = 11 -> dp[3] = 11
k=4 house worth 3: skip = dp[3] = 11, take = dp[2] + 3 = 10 -> dp[4] = 11
k=5 house worth 1: skip = dp[4] = 11, take = dp[3] + 1 = 12 -> dp[5] = 12
answer = 12 (houses worth 2, 9 and 1)
@@ -0,0 +1,18 @@
Coin Change: greedy, the overflow bug, call counts, loop order
==============================================================
1) Greedy fails: coins {1,3,4}, amount 6
greedy -> 3 coins (4+1+1)
DP (table) -> 2 coins (3+3)
2) 'Impossible' = Integer.MAX_VALUE, then dp[a-c] + 1: coins {2}, amount 3 (really impossible)
correct (impossible = amount + 1) -> -1
Integer.MAX_VALUE version -> -2147483648
3) Calls made: coins {1,2,5}, amount 27, answer 6
naive recursion -> 2688332 calls
memoized -> 77 calls
4) Loop order changes the question: coins {1,2,5}, amount 5
coins in the OUTER loop -> 4 (combinations: 5 / 2+2+1 / 2+1+1+1 / 1x5)
amount in the OUTER loop -> 9 (permutations: 1+2 and 2+1 now count separately)
@@ -0,0 +1,19 @@
0/1 Knapsack
============
1) Table: weights [1,3,4,5], values [1,4,5,7], capacity 7. dp[i][w] = best value with the first i items
w = 0 1 2 3 4 5 6 7
none 0 0 0 0 0 0 0 0
item 1 0 1 1 1 1 1 1 1
item 2 0 1 1 4 5 5 5 5
item 3 0 1 1 4 5 6 6 9
item 4 0 1 1 4 5 7 8 9
answer = dp[4][7] = 9 (weights 3 + 4, values 4 + 5)
2) One row, capacity loop direction: weights [5,4,6,3], values [10,40,30,50], capacity 10
backward loop (each item used once) -> 90
forward loop, same code otherwise -> 150 (the weight-3 item is reused three times: 3 x 50)
3) Calls made: 22 items, capacity 60, best value 357
naive recursion -> 1003985 calls
memoized -> 1794 calls
+20
View File
@@ -0,0 +1,20 @@
Longest Common Subsequence: "ABCBDAB" and "BDCABA"
==================================================
- B D C A B A
- 0 0 0 0 0 0 0
A 0 0 0 0 1 1 1
B 0 1 1 1 1 2 2
C 0 1 1 2 2 2 2
B 0 1 1 2 2 3 3
D 0 1 2 2 2 3 3
A 0 1 2 2 3 3 4
B 0 1 2 2 3 4 4
length (bottom-right cell) = 4
reconstructed from the full table = "BCBA" (a valid subsequence of both strings)
two-row version = 4 (length only; cannot rebuild the string)
Calls made on two 15-character strings "ACGTTGCAACGTGCA" and "TGCATGCCGATGACG" (answer 9):
naive recursion -> 293086 calls
memoized -> 247 calls
@@ -0,0 +1,16 @@
Edit Distance: "kitten" -> "sitting"
====================================
- s i t t i n g
- 0 1 2 3 4 5 6 7
k 1 1 2 3 4 5 6 7
i 2 2 1 2 3 4 5 6
t 3 3 2 1 2 3 4 5
t 4 4 3 2 1 2 3 4
e 5 5 4 3 2 2 3 4
n 6 6 5 4 3 3 2 3
answer = 3 (k->s, e->i, insert g)
two-row version = 3
horse -> ros = 3
intention -> execution = 5
+8
View File
@@ -0,0 +1,8 @@
Longest Increasing Subsequence: [10, 9, 2, 5, 3, 7, 101, 18]
============================================================
a[i] [10, 9, 2, 5, 3, 7, 101, 18]
dp[i] [1, 1, 1, 2, 2, 3, 4, 4] (dp[i] = longest increasing subsequence ENDING at index i)
answer = max(dp) = 4 (for example 2, 3, 7, 18)
O(n log n) patience version = 4
@@ -0,0 +1,10 @@
Unique Paths on a 3 x 7 grid (right and down only)
==================================================
0 1 2 3 4 5 6
row 0 1 1 1 1 1 1 1
row 1 1 2 3 4 5 6 7
row 2 1 3 6 10 15 21 28
answer = bottom-right cell = 28
10 x 10 grid: table = 48620, one row = 48620, binomial C(18,9) = 48620
@@ -0,0 +1,14 @@
Partition Equal Subset Sum: [1, 5, 11, 5], target 11
====================================================
T = some subset of the first i numbers sums to s
s = 0 1 2 3 4 5 6 7 8 9 10 11
none T . . . . . . . . . . .
+1 T T . . . . . . . . . .
+5 T T . . . T T . . . . .
+11 T T . . . T T . . . . T
+5 T T . . . T T . . . T T
[1, 5, 11, 5] -> true (5 + 5 + 1 = 11 = 11)
[1, 2, 3, 5] -> false (total 11 is odd)
[1, 2, 4] -> false (total 7 is odd)
@@ -0,0 +1,15 @@
Cross-check: 300 random inputs per problem, every variant must agree (seed 2026)
================================================================================
problem cases variants compared
Climbing Stairs 300 memo = table = two variables (n up to 39)
House Robber 300 memo = table = two variables
Coin Change (fewest) 300 naive = memo = table (amount up to 29)
Longest Increasing Subseq. 300 memo = O(n^2) table = O(n log n) patience
0/1 Knapsack 300 naive = memo = 2-D table = one row (backward)
Longest Common Subsequence 300 naive = memo = full table = two rows
Edit Distance 300 memo = full table = two rows
Unique Paths 300 memo = table = one row = binomial formula
Partition Equal Subset Sum 300 memo = 2-D table = one row (backward)
Fibonacci is cross-checked in 01-fibonacci-call-counts.txt (naive = memo = table = two variables).
@@ -0,0 +1,6 @@
max heap (MiB) : 48
strings : two of length 5000
full table cells : 25010001 ints = 100 MB
two-row cells : 10002 ints = 40 KB
two rows LCS length : 2500
full table LCS length : java.lang.OutOfMemoryError: Java heap space