Overlapping Subproblems and Optimal Substructure in DSA Typescript - Time & Space Complexity
We want to understand how solving problems with overlapping parts affects the time it takes.
How does breaking a problem into smaller parts and reusing answers change the work needed?
Analyze the time complexity of this Fibonacci function using simple recursion.
function fib(n: number): number {
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 the repeated calls that happen again and again.
- Primary operation: Recursive calls to fib for n-1 and n-2.
- How many times: Many calls repeat the same fib values multiple times.
Each number calls two smaller numbers, doubling work roughly.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 177 calls |
| 20 | Over 21,000 calls |
| 30 | More than 2 million calls |
Pattern observation: The work grows very fast, almost doubling with each increase in n.
Time Complexity: O(2^n)
This means the time needed grows very fast, doubling roughly for each step up in n.
[X] Wrong: "The recursive calls only happen once each, so time is O(n)."
[OK] Correct: Actually, many calls repeat the same calculations again and again, causing exponential growth.
Understanding overlapping subproblems helps you spot when to use smarter methods like memoization to save time.
What if we add a memory to save results already calculated? 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 with options
The problem can be broken down into smaller subproblems that are solved multiple times. correctly states that subproblems repeat and can be reused.Final Answer:
The problem can be broken down into smaller subproblems that are solved multiple times. -> Option CQuick Check:
Overlapping subproblems = repeated smaller problems [OK]
- Confusing overlapping subproblems with unique subproblems
- Thinking sorting is required for overlapping subproblems
- Assuming recursion alone solves overlapping subproblems
Solution
Step 1: Identify suitable data structure for memoization
Memoization stores key-value pairs of subproblems and results, so Map is ideal.Step 2: Check options for correct TypeScript syntax
let memo = new Map(); uses Map which is correct for memoization.Final Answer:
let memo = new Map<number, number>(); -> Option AQuick Check:
Memoization uses Map for key-value storage [OK]
- Using empty object without type safety
- Declaring memo as a string instead of a collection
- Using array when keys are not sequential indices
function fib(n: number, memo = new Map()): number {
if (n <= 1) return n;
if (memo.has(n)) return memo.get(n)!;
const result = fib(n - 1, memo) + fib(n - 2, memo);
memo.set(n, result);
return result;
}
console.log(fib(5));What is the output printed?
Solution
Step 1: Understand Fibonacci calculation with memoization
fib(5) = fib(4) + fib(3), recursively calculated and stored to avoid repeats.Step 2: Calculate fib(5) step-by-step
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5.Final Answer:
5 -> Option BQuick Check:
fib(5) = 5 [OK]
- Confusing fib(5) with fib(6)
- Forgetting base cases n <= 1
- Assuming memo is reset each call
function fib(n: number, memo = new Map()): number {
if (n <= 1) return n;
if (memo.has(n)) return memo.get(n)!;
const result = fib(n - 1) + fib(n - 2);
memo.set(n, result);
return result;
}
console.log(fib(5));What is the main issue causing incorrect behavior?
Solution
Step 1: Analyze recursive calls and memo usage
Recursive calls fib(n - 1) and fib(n - 2) do not pass the memo Map, so each call creates a new Map.Step 2: Understand impact on memoization
Without passing memo, results are not stored or reused, causing repeated calculations and no optimization.Final Answer:
Memo object is not passed in recursive calls, so memoization fails. -> Option AQuick Check:
Pass memo in recursive calls to reuse stored results [OK]
- Not passing memo in recursive calls
- Changing base case unnecessarily
- Assuming Map auto-shares across calls
const cost = [ [1, 3, 5], [2, 1, 2], [4, 3, 1] ];
Which approach best uses
overlapping subproblems and optimal substructure to solve this efficiently?Solution
Step 1: Identify problem properties
Minimum cost path has optimal substructure: cost to reach a cell depends on minimum cost to reach adjacent cells.Step 2: Choose approach using overlapping subproblems
Dynamic programming stores results for each cell to avoid recomputing paths (overlapping subproblems).Final Answer:
Use dynamic programming to store minimum cost for each cell based on previous cells. -> Option DQuick Check:
DP uses overlapping subproblems + optimal substructure [OK]
- Using recursion without memoization causing slow performance
- Assuming greedy always finds minimum cost
- Sorting grid values unrelated to path costs
Ready to prove it?
Pass the test and this pattern moves unknown
