Overlapping Subproblems and Optimal Substructure in DSA Javascript - 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 needed?
Analyze the time complexity of the following code snippet.
function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n] !== undefined) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
This code calculates the nth Fibonacci number using a method that saves results to avoid repeating work.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to fib with saved results (memoization).
- How many times: Each unique fib(n) is calculated once; repeated calls use saved answers.
Each number from 0 up to n is calculated once, so work grows roughly in a straight line with n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 calculations |
| 100 | About 100 calculations |
| 1000 | About 1000 calculations |
Pattern observation: Work grows steadily and linearly as input size increases.
Time Complexity: O(n)
This means the time to get the answer grows in a straight line with the input size.
[X] Wrong: "The recursive Fibonacci always takes exponential time because it calls itself many times."
[OK] Correct: With saving results (memoization), each number is calculated once, so the time is much less than exponential.
Understanding how saving repeated work changes time helps you solve many problems efficiently and impress interviewers.
"What if we removed the memo object and calculated fib(n) with plain recursion? 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 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 to save time.Final Answer:
The problem can be broken down into smaller subproblems that are solved multiple times. -> Option AQuick Check:
Overlapping subproblems = repeated smaller problems [OK]
- Confusing overlapping subproblems with unique subproblems
- Thinking sorting is required for all DP problems
- Assuming recursion alone solves overlapping subproblems
Solution
Step 1: Identify memoization pattern
Memoization stores results in a cache object to avoid repeated calculations.Step 2: Check each option
const memo = {}; function fib(n) { if (n in memo) return memo[n]; if (n <= 1) return n; memo[n] = fib(n-1) + fib(n-2); return memo[n]; } uses an object 'memo' to store and reuse Fibonacci results correctly.Final Answer:
const memo = {}; function fib(n) { if (n in memo) return memo[n]; if (n <= 1) return n; memo[n] = fib(n-1) + fib(n-2); return memo[n]; } -> Option BQuick Check:
Memoization uses cache object = const memo = {}; function fib(n) { if (n in memo) return memo[n]; if (n <= 1) return n; memo[n] = fib(n-1) + fib(n-2); return memo[n]; } [OK]
- Not using a cache to store results
- Incorrect base case handling
- Using loops instead of recursion for memoization
function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
console.log(fib(5));Solution
Step 1: Trace fib(5) calls with memo
fib(5) = fib(4) + fib(3), fib(4) = fib(3) + fib(2), fib(3) = fib(2) + fib(1), etc.Step 2: Calculate Fibonacci values
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5Final Answer:
5 -> Option CQuick Check:
fib(5) = 5 [OK]
- Confusing fib(5) with fib(6)
- Ignoring memoization effect
- Miscounting base cases
function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
console.log(fib(0));Solution
Step 1: Trace fib(0)
fib(0): n = 0 <= 1, so return 0 immediately. Memo check and recursion not reached.Step 2: Output
console.log(0) prints 0.Final Answer:
0 -> Option AQuick Check:
fib(0) = 0 [OK]
- Thinking memo[n] check runs for base cases n <= 1
- Believing falsy memo[0] causes undefined return
- Forgetting base case includes n = 0
Solution
Step 1: Understand minimum cost path problem
The cost to reach a cell depends on the best path to its neighbors (top or left).Step 2: Identify optimal substructure
The problem can be solved by combining optimal solutions of smaller subproblems (cells).Final Answer:
Optimal substructure: The minimum cost path to a cell depends on minimum cost paths to adjacent cells. -> Option DQuick Check:
Optimal substructure enables DP for grid path [OK]
- Confusing overlapping subproblems with unique subproblems
- Assuming greedy always works for grid path
- Ignoring dependency between subproblems
Ready to prove it?
Pass the test and this pattern moves unknown
