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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user