Dynamic programming — overlapping subproblems, optimal substructure
WHY does DP exist?
Plain recursion often recomputes the same thing exponentially many times. DP exists to kill that waste.
The TWO properties (you MUST verify both before using DP)
HOW to turn recursion into DP — two styles
Deriving the speedup from first principles

Worked Example 1 — Fibonacci, three ways
Naive recursion ():
def fib(n):
if n < 2: return n
return fib(n-1) + fib(n-2)Memoization (top-down):
def fib(n, memo={}):
if n < 2: return n
if n in memo: return memo[n] # reuse — kills overlap
memo[n] = fib(n-1, memo) + fib(n-2, memo)
return memo[n]Why this step? The if n in memo check converts an exponential tree into a linear chain: each is computed once, then served from cache.
Tabulation (bottom-up):
def fib(n):
if n < 2: return n
dp = [0, 1]
for i in range(2, n+1):
dp.append(dp[i-1] + dp[i-2]) # smallest → largest
return dp[n]Why this step? We fill in increasing so are already computed — that ordering is exactly "optimal substructure made concrete."
Worked Example 2 — 0/1 Knapsack (showing both properties)
Items with weight , value , capacity . Maximize value.
Derive the recurrence. Consider item with remaining capacity . Two choices:
- Skip it: best value is .
- Take it (only if ): gain + best of the rest with less room: .
Why is this optimal substructure? The best packing of items must contain a best packing of for whatever capacity is left — otherwise we could swap in the better sub-packing and improve the whole. Why overlapping? Many different item-prefixes hit the same pair.
def knapsack(w, v, W):
n = len(w)
dp = [[0]*(W+1) for _ in range(n+1)]
for i in range(1, n+1):
for c in range(W+1):
dp[i][c] = dp[i-1][c] # skip
if w[i-1] <= c:
dp[i][c] = max(dp[i][c],
v[i-1] + dp[i-1][c-w[i-1]]) # take
return dp[n][W]Time subproblems .
Steel-manned mistakes
Recall Feynman: explain it to a 12-year-old
Imagine homework where the same little math problem shows up 50 times. A lazy-but-smart kid solves it once, writes the answer on a sticky note, and just copies it the other 49 times. That's memoization. Tabulation is doing all the tiny problems first, in order, before the big one — like building Lego from the bottom blocks up. The trick only helps because (1) the same little problems keep repeating, and (2) the big answer is genuinely made of the little answers.
Flashcards
What are the two properties a problem needs for DP to apply?
Define overlapping subproblems.
Define optimal substructure.
What's the difference between memoization and tabulation?
General formula for DP time complexity?
Fibonacci: naive vs DP time complexity?
Why doesn't Merge Sort benefit from DP?
Why does greedy fail for 0/1 knapsack but DP works?
Give a problem with overlap but NO optimal substructure.
What ordering rule must tabulation obey?
0/1 Knapsack recurrence?
Time complexity of 0/1 knapsack DP?
Connections
- Recursion — DP is memory-augmented recursion.
- Divide and Conquer — subproblems are disjoint (no overlap) → not DP.
- Greedy Algorithms — needs greedy-choice property; DP needs only optimal substructure.
- Memoization vs Tabulation
- Knapsack Problem, Longest Common Subsequence, Bellman-Ford
- Time Complexity Analysis
- Recursion Trees
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, Dynamic Programming ka core idea bilkul simple hai: recursion + yaad rakhna. Jab tum koi problem recursion se solve karte ho aur wahi chhoti problem baar-baar repeat hoti hai (yeh hai overlapping subproblems), toh har baar use dobara solve karna pure waste hai. Fibonacci dekho — nikalne mein kai baar compute hota hai, aur tree exponential ban jaata hai, . DP bolta hai: ek sticky note pe answer likh lo (cache/table), aur agli baar bas copy kar lo. Bas isse time ho jaata hai.
Do cheezein zaroori hain. Pehli, optimal substructure — matlab bade problem ka best answer chhote problems ke best answers se banta hai. Yeh guarantee karta hai ki sub-answers jodna sahi hai. Doosri, overlapping subproblems — matlab same chhoti problem baar-baar aati hai, tabhi caching faydemand hai. Dono chahiye: bina substructure ke DP galat answer dega (jaise longest simple path), aur bina overlap ke (jaise Merge Sort) DP sirf extra memory khaayega, speed nahi badhegi.
Do styles hain. Memoization (top-down): normal recursion likho, par answer ko map/array mein store karo, aur compute se pehle check karo "kya pehle se hai?". Tabulation (bottom-up): table ko sabse chhoti problem se shuru karke upar tak bharo, aise order mein ki jab tumhe dp[i-1] chahiye toh woh pehle se ready ho. Yaad rakho: fill order recurrence ki dependency ko respect kare, warna galat answer aayega.
Ek important warning — greedy se dhokha mat khaana. Fractional knapsack mein greedy chalta hai, par 0/1 knapsack mein items tod nahi sakte, isliye greedy fail ho jaata hai aur DP () lagana padta hai. Mnemonic simple: "OO" → DP OK (Overlapping + Optimal). Exam aur interviews dono mein yeh paradigm bahut aata hai, toh recurrence khud derive karna seekho, ratta mat maaro.