Overlapping Subproblems and Optimal Substructure in DSA Python - Time & Space Complexity
We want to understand how solving problems with repeated parts affects the time it takes.
How does breaking a problem into smaller parts and reusing answers change the work done?
Analyze the time complexity of this Fibonacci function using simple recursion.
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
This code calculates the nth Fibonacci number by calling itself twice for smaller values.
Look at what repeats when the function calls itself.
- Primary operation: Recursive calls to fib(n-1) and fib(n-2)
- How many times: Many calls repeat the same fib values multiple times, especially for smaller n.
Each call splits into two more calls, growing very fast.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 177 calls |
| 20 | Over 21,000 calls |
| 30 | More than 2 million calls |
Pattern observation: The number of calls grows roughly like the Fibonacci sequence, causing very fast growth.
Time Complexity: O(2^n)
This means the work doubles with each increase in n, making it very slow for large n.
[X] Wrong: "Since fib(n) calls fib(n-1) and fib(n-2), it should be about 2n calls."
[OK] Correct: Each call creates two more calls, so the total calls grow exponentially, not linearly.
Understanding this helps you see why some problems need smarter methods to avoid repeating work.
"What if we store results of fib(n) after computing them once? How would the time complexity change?"
Practice
overlapping subproblems property in dynamic programming?Solution
Step 1: Understand overlapping subproblems
Overlapping subproblems means the same smaller problems appear multiple times during solving.Step 2: Match definition to options
The problem can be broken down into smaller subproblems that are reused multiple times. correctly states that subproblems are reused multiple times, which is the key idea.Final Answer:
The problem can be broken down into smaller subproblems that are reused multiple times. -> Option AQuick Check:
Overlapping subproblems = reuse of subproblems [OK]
- Confusing overlapping subproblems with unique subproblems
- Thinking sorting is required for overlapping subproblems
- Assuming recursion alone solves overlapping subproblems
Solution
Step 1: Identify memoization usage
Memoization stores results of subproblems to avoid repeated work, usually with a dictionary.Step 2: Check each option
def fib(n, memo={}): if n in memo: return memo[n] if n <= 1: memo[n] = n else: memo[n] = fib(n-1, memo) + fib(n-2, memo) return memo[n] uses a dictionary 'memo' to save and reuse results, correctly implementing memoization.Final Answer:
def fib(n, memo={}): if n in memo: return memo[n] if n <= 1: memo[n] = n else: memo[n] = fib(n-1, memo) + fib(n-2, memo) return memo[n] -> Option DQuick Check:
Memoization = caching results [OK]
- Missing memo dictionary parameter
- Not returning cached results
- Using loops without caching results
def fib(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
memo[n] = n
else:
memo[n] = fib(n-1, memo) + fib(n-2, memo)
return memo[n]
print(fib(5))Solution
Step 1: Trace fib(5) calls with memoization
fib(5) calls fib(4) and fib(3), which further call smaller fib values, caching results to avoid repeats.Step 2: Calculate fib(5)
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5Final Answer:
5 -> Option CQuick Check:
Fibonacci(5) = 5 [OK]
- Confusing fib(5) with fib(6)
- Ignoring memoization effect
- Miscounting base cases
def fib(n, memo={}):
if n <= 1:
return n
if n not in memo:
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
print(fib(4))Solution
Step 1: Check recursive calls
Recursive calls fib(n-1) and fib(n-2) do not pass 'memo', so memo is empty each time.Step 2: Understand effect of missing memo argument
Without passing memo, caching fails and recursion repeats infinitely, causing a stack overflow.Final Answer:
Missing memo argument in recursive calls causes infinite recursion. -> Option AQuick Check:
Pass memo in recursive calls to fix [OK]
- Forgetting to pass memo in recursive calls
- Assuming base case alone stops recursion
- Not initializing memo outside function
optimal substructure and overlapping subproblems to solve this?Solution
Step 1: Understand problem structure
Minimum cost to reach step i depends on minimum cost to reach steps i-1 and i-2 plus cost at i.Step 2: Apply dynamic programming
Store minimum costs for each step to avoid recomputing (overlapping subproblems) and use previous results (optimal substructure).Final Answer:
Use dynamic programming to store minimum cost for each step and build up solution. -> Option BQuick Check:
DP uses optimal substructure + overlapping subproblems [OK]
- Using recursion without memoization causing slow solution
- Trying greedy approach which may fail
- Sorting costs which is irrelevant
Ready to prove it?
Pass the test and this pattern moves unknown
