Overlapping Subproblems and Optimal Substructure in DSA C++ - 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 overlapping pieces change the work needed?
Analyze the time complexity of the following Fibonacci function using simple recursion.
int fib(int 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 for repeated work in the code.
- Primary operation: Recursive calls to fib for n-1 and n-2.
- How many times: Many calls repeat the same fib values multiple times, causing exponential repeats.
Each call splits into two more calls, 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, roughly doubling with each increase in n.
Time Complexity: O(2^n)
This means the time needed grows very fast as n gets bigger, doubling roughly each step.
[X] Wrong: "Since fib calls itself twice, the time is just twice the previous time."
[OK] Correct: Each call creates two new calls, so the total calls grow exponentially, not just doubled once.
Understanding overlapping subproblems helps you spot when to use smarter methods like memoization to save time.
"What if we store results of fib calls to avoid repeats? How would the time complexity change?"
Practice
Overlapping Subproblems in dynamic programming?Solution
Step 1: Understand Overlapping Subproblems
Overlapping subproblems means the same smaller problems appear multiple times during the solution process.Step 2: Match the definition to options
The problem can be broken down into smaller subproblems which are solved multiple times. correctly states that subproblems are solved multiple times, which is the key idea behind overlapping subproblems.Final Answer:
The problem can be broken down into smaller subproblems which are solved multiple times. -> Option AQuick Check:
Overlapping Subproblems = Smaller repeated subproblems [OK]
- Confusing overlapping subproblems with unique subproblems
- Thinking sorting is necessary for overlapping subproblems
- Assuming recursion alone solves overlapping subproblems
Solution
Step 1: Identify memoization parameters
Memoization requires an extra data structure (like an array) to store results of subproblems.Step 2: Check function signatures
int fib(int n, int memo[]); includes an array parameter to store memoized results, which is necessary for memoization.Final Answer:
int fib(int n, int memo[]); -> Option DQuick Check:
Memoization needs storage parameter [OK]
- Ignoring the memo array parameter
- Using void return type for Fibonacci
- Not passing any parameters for recursion
int fib(int n, int memo[]) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fib(n-1, memo) + fib(n-2, memo);
return memo[n];
}
int main() {
int n = 5;
int memo[6];
for(int i = 0; i <= n; i++) memo[i] = -1;
std::cout << fib(n, memo);
return 0;
}Solution
Step 1: Understand Fibonacci calculation
fib(5) = fib(4) + fib(3). Using memoization, fib(5) = 8.Step 2: Trace the code execution
Memo array initialized with -1, so recursive calls compute and store values. fib(5) returns 8.Final Answer:
8 -> Option BQuick Check:
fib(5) = 8 [OK]
- Confusing fib(5) with fib(6)
- Forgetting zero-based indexing
- Assuming memo array is not initialized
int fib(int n, int memo[]) {
if (n == 0) return 0;
if (n == 1) return 1;
if (memo[n] != 0) return memo[n];
memo[n] = fib(n-1, memo) + fib(n-2, memo);
return memo[n];
}Solution
Step 1: Check memoization condition
The code checks if memo[n] != 0 to decide if value is computed, but 0 is a valid Fibonacci value.Step 2: Correct initialization for memo array
Memo array should be initialized with a value like -1 to indicate uncomputed states, avoiding confusion with valid 0.Final Answer:
Memo array should be initialized with -1, not 0. -> Option CQuick Check:
Use -1 for uninitialized memo values [OK]
- Using 0 as default memo value causing wrong cache hits
- Changing base case return values incorrectly
- Ignoring memo array size checks
Solution
Step 1: Understand minimum cost path problem
The problem breaks into smaller subproblems (paths to cells) that overlap and solutions combine optimally.Step 2: Identify properties
It has overlapping subproblems (same cells computed multiple times) and optimal substructure (optimal path uses optimal subpaths).Final Answer:
The problem has both overlapping subproblems and optimal substructure. -> Option AQuick Check:
Minimum cost path = Overlapping + Optimal substructure [OK]
- Thinking only one property applies
- Confusing optimal substructure with greedy choice
- Ignoring repeated subproblems in grid paths
Ready to prove it?
Pass the test and this pattern moves unknown
