Divide and Conquer Strategy and Recurrence Relations in DSA C++ - Time & Space Complexity
We want to understand how dividing a problem into smaller parts affects the time it takes to solve it.
How does breaking a problem down and combining results change the work needed?
Analyze the time complexity of the following code snippet.
int divideAndConquer(int n) {
if (n <= 1) return 1;
int left = divideAndConquer(n / 2);
int right = divideAndConquer(n / 2);
return left + right + n;
}
This function splits the problem into two halves, solves each half recursively, and then does work proportional to n to combine results.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Two recursive calls each on half the input size.
- How many times: Recursion continues until input size reduces to 1.
- Additional work: After recursion, does work proportional to n (combining step).
Each time the input size halves, the function calls itself twice, and does work proportional to the current input size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 + 2*5 + 4*2 + 8*1 = 34 |
| 100 | About 100 + 2*50 + 4*25 + ... ≈ 400 |
| 1000 | About 1000 + 2*500 + 4*250 + ... ≈ 8000 |
Pattern observation: The total work grows a bit more than linearly, increasing roughly by n log n as input grows.
Time Complexity: O(n log n)
This means the time grows a little faster than the input size, multiplying by the number of times the problem is split.
[X] Wrong: "Since the problem splits in half, the time must be O(log n)."
[OK] Correct: The function does extra work proportional to n at each level, so total time adds up more than just the splits.
Understanding how dividing problems affects time helps you explain and design efficient algorithms clearly.
"What if the function made three recursive calls each on one-third of the input? How would the time complexity change?"
Practice
Divide and Conquer strategy in algorithms?Solution
Step 1: Understand Divide and Conquer concept
Divide and conquer splits a problem into smaller parts, solves each part, then merges results.Step 2: Compare options
Break the problem into smaller subproblems, solve them independently, then combine results correctly describes this process. Options B, C, and D describe other strategies like brute force or dynamic programming.Final Answer:
Break the problem into smaller subproblems, solve them independently, then combine results -> Option AQuick Check:
Divide and Conquer = Break, Solve, Combine [OK]
- Confusing divide and conquer with brute force
- Thinking it uses only loops without recursion
- Mixing it up with dynamic programming
Solution
Step 1: Recall Merge Sort recurrence
Merge Sort divides array into two halves, sorts each half (2T(n/2)) and merges in O(n).Step 2: Match with options
T(n) = 2T(n/2) + O(n) matches 2T(n/2) + O(n). Others describe different algorithms or complexities.Final Answer:
T(n) = 2T(n/2) + O(n) -> Option CQuick Check:
Merge Sort recurrence = 2T(n/2) + n [OK]
- Using T(n-1) instead of T(n/2)
- Confusing merge cost with recursive calls
- Choosing quadratic terms incorrectly
int count = 0;
int func(int n) {
if (n <= 1) return 1;
count++;
return func(n/2) + func(n/2);
}
int main() {
func(8);
std::cout << count;
return 0;
}Solution
Step 1: Understand recursion calls and count increments
func(8) calls func(4) twice, each func(4) calls func(2) twice, and so on until n <= 1.Step 2: Count increments at each call
Count increments at each non-base call. The recursion tree has 15 non-base calls (all calls except those where n <= 1). Thus, count increments 15 times.Final Answer:
15 -> Option AQuick Check:
Count increments for each non-base call = 15 [OK]
- Counting base case calls
- Not doubling calls for each recursion
- Miscounting due to integer division
T(n) = 2T(n/2) + n^2 for n > 1 T(1) = 1What is the correct time complexity?
Solution
Step 1: Analyze the recurrence relation
T(n) = 2T(n/2) + n^2 means dividing problem into two halves and combining in n^2 time.Step 2: Apply Master Theorem
Here, a=2, b=2, log_b a = 1, f(n)=n^2 = Ω(n^{1 + ε}) for some ε > 0 (case 3). Thus, time complexity is Θ(n^2) = O(n^2).Final Answer:
O(n^2) -> Option BQuick Check:
Master Theorem with f(n)=n^2 gives O(n^2) [OK]
- Assuming O(n log n) without checking f(n)
- Ignoring the power of n in f(n)
- Misapplying Master Theorem cases
T(n) = 3T(n/3) + n / log n
Using the Master Theorem, what is the asymptotic time complexity of T(n)?
Solution
Step 1: Identify parameters for Master Theorem
a=3, b=3, so n^{log_b a} = n^{log_3 3} = n^1 = n.Step 2: Compare f(n) = n / log n with n^{log_b a} = n
f(n) grows slower than n by a factor of log n. This is a case where f(n) = n / log n = n^{1} / log n.Step 3: Apply Master Theorem case 2 variant
Since f(n) = n / log n = n^{1} / log n, which is smaller than n^{1} by a log factor, the complexity is O(n log log n).Final Answer:
O(n log log n) -> Option DQuick Check:
Master Theorem with f(n) = n / log n gives O(n log log n) [OK]
- Ignoring the log factor in f(n)
- Choosing O(n) or O(n log n) incorrectly
- Miscomputing log base
Ready to prove it?
Pass the test and this pattern moves unknown
