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 parts and reusing answers change the work needed?
Analyze the time complexity of the following code snippet.
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
int fib_memo(int n, int memo[]) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo);
return memo[n];
}
This code calculates Fibonacci numbers using simple recursion and recursion with memoization to avoid repeated work.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to fib or fib_memo functions.
- How many times: In simple recursion, many calls repeat the same calculations; in memoized version, each unique call happens once.
Without memoization, the calls grow very fast because the same problems are solved many times.
| Input Size (n) | Approx. Operations (simple recursion) |
|---|---|
| 10 | ~177 calls |
| 20 | ~21,891 calls |
| 30 | ~2,692,537 calls |
With memoization, the number of calls grows only linearly with n because each result is stored and reused.
| Input Size (n) | Approx. Operations (memoized) |
|---|---|
| 10 | 10 calls |
| 20 | 20 calls |
| 30 | 30 calls |
Pattern observation: Simple recursion grows exponentially, memoized recursion grows linearly.
Time Complexity: O(n) with memoization
This means the work grows in a straight line with input size when we reuse answers, avoiding repeated work.
[X] Wrong: "Recursion always means exponential time because it calls itself many times."
[OK] Correct: When we save results and reuse them, recursion can be very efficient and run in linear time.
Understanding how to spot repeated work and use memory to save time is a key skill that shows you can write smart, efficient code.
"What if we changed the memo array to a hash map? 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 with 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 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: Understand C array declaration
In C, arrays are declared with fixed size and can be initialized with {0} to set all elements to zero.Step 2: Check options for valid C syntax
int memo[1000] = {0}; uses correct C syntax: int memo[1000] = {0}; initializes an array of 1000 integers to zero.Final Answer:
int memo[1000] = {0}; -> Option DQuick Check:
C array declaration with initialization = int memo[1000] = {0}; [OK]
- Using C++ style vector or new keyword in C
- Declaring arrays without size
- Incorrect initialization syntax
int memo[100] = {0};
int fib(int n) {
if (n <= 1) return n;
if (memo[n] != 0) return memo[n];
memo[n] = fib(n-1) + fib(n-2);
return memo[n];
}
int main() {
printf("%d", fib(5));
return 0;
}
What is the output when this code runs?Solution
Step 1: Trace fib(5) calls with memoization
fib(5) calls fib(4) + fib(3). fib(4) calls fib(3) + fib(2), and so on. Base cases fib(0)=0, fib(1)=1.Step 2: Calculate fib(5)
fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=fib(4)+fib(3)=3+2=5.Final Answer:
5 -> Option BQuick Check:
Fibonacci(5) = 5 [OK]
- Confusing index with Fibonacci value
- Ignoring memoization effect
- Miscomputing base cases
int memo[100] = {0};
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
if (memo[n] != 0) return memo[n];
memo[n] = fib(n-1) + fib(n-2);
return memo[n];
}
int main() {
printf("%d", fib(0));
return 0;
}
What is the bug in this code?Solution
Step 1: Analyze base cases and memo usage
fib(0) returns 0 but does not store it in memo[0]. So memo[0] remains 0, which is the default value.Step 2: Understand effect on memoization
Because the base case results are not stored in memo[n], fib(0) (and fib(1)) will be called repeatedly in the recursion tree for larger n, instead of returning a cached value.Final Answer:
fib(0) returns 0 but memo[0] is never set, causing repeated calls. -> Option AQuick Check:
Base case not stored in memo causes repeated calls [OK]
- Assuming memo array auto-initializes for base cases
- Missing base case for fib(1)
- Incorrect memo check condition
cost[] = {10, 15, 20}. Using dynamic programming with overlapping subproblems and optimal substructure, what is the minimum cost to reach the top (beyond last step)?Solution
Step 1: Define subproblems and recurrence
Minimum cost to reach step i: dp[i] = cost[i] + min(dp[i-1], dp[i-2]). dp[0]=10, dp[1]=15.Step 2: Calculate minimum cost step by step
dp[2] = 20 + min(10,15) = 30. To reach top (beyond last step), min(dp[1], dp[2]) = min(15,30) = 15.Final Answer:
15 -> Option AQuick Check:
Minimum cost to top = 15 [OK]
- Adding all costs instead of choosing min path
- Ignoring that you can start from step 0 or 1
- Confusing cost to step vs cost beyond last step
Ready to prove it?
Pass the test and this pattern moves unknown
