Merge Sort as Divide and Conquer in DSA C - Time & Space Complexity
We want to understand how the time needed by merge sort grows as the list gets bigger.
Specifically, how does splitting and merging affect the total work done?
Analyze the time complexity of the following code snippet.
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1;
int n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[l + i];
for (int j = 0; j < n2; j++) R[j] = arr[m + 1 + j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
This code splits the array into halves, sorts each half recursively, then merges them back.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The merge function compares and copies elements to combine two sorted halves.
- How many times: MergeSort calls itself twice on halves, creating a tree of calls with depth about log n.
- Each merge step processes all elements in the current segment once.
Each level of splitting does work proportional to n, and there are about log n levels.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 * 4 = 40 |
| 100 | About 100 * 7 = 700 |
| 1000 | About 1000 * 10 = 10,000 |
Pattern observation: Operations grow a bit more than linearly, roughly n times log n.
Time Complexity: O(n log n)
This means if the list doubles in size, the work grows a bit more than double, because of the splitting and merging steps.
[X] Wrong: "Merge sort is just like doing a simple loop over the array, so it is O(n)."
[OK] Correct: Merge sort splits the array many times and merges at each level, so the total work adds up to more than just one pass.
Understanding merge sort's time complexity shows you how divide and conquer helps solve problems efficiently, a key skill in many coding challenges.
"What if we changed merge sort to split the array into three parts instead of two? How would the time complexity change?"
Practice
Merge Sort algorithm?Solution
Step 1: Understand the divide and conquer approach
Merge Sort splits the array into two halves recursively until single elements remain.Step 2: Merge sorted halves
After splitting, it merges the sorted halves back to form a sorted array.Final Answer:
Divide the array into halves, sort each half, then merge them -> Option AQuick Check:
Merge Sort = Divide and Conquer [OK]
- Confusing Merge Sort with Bubble Sort
- Thinking Merge Sort uses a pivot like Quick Sort
- Believing Merge Sort sorts in place without merging
Solution
Step 1: Identify when recursion should stop
Merge Sort stops splitting when the subarray has one or zero elements, i.e., start >= end.Step 2: Choose the correct condition
The base case is when start >= end, so return without further splitting.Final Answer:
if (start >= end) return; -> Option AQuick Check:
Base case = start >= end [OK]
- Using == instead of >= risks issues if start > end
- Using > misses the single element case
- Using != returns too early, breaking recursion
mergeSort(arr, 0, 4); on int arr[] = {5, 3, 8, 4, 2};?
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[l + i];
for (int j = 0; j < n2; j++) R[j] = arr[m + 1 + j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
int main() {
int arr[] = {5, 3, 8, 4, 2};
mergeSort(arr, 0, 4);
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
return 0;
}Solution
Step 1: Understand mergeSort splits and merges
The array is recursively split into halves and merged in sorted order.Step 2: Trace the final sorted array
After full recursion and merging, the array becomes sorted: 2 3 4 5 8.Final Answer:
2 3 4 5 8 -> Option CQuick Check:
Sorted array output = 2 3 4 5 8 [OK]
- Thinking mergeSort does not change original array
- Confusing mergeSort with reverse sorting
- Ignoring the merge step results in unsorted output
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i <= n1; i++) L[i] = arr[l + i];
for (int j = 0; j <= n2; j++) R[j] = arr[m + 1 + j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}Solution
Step 1: Check array copying loops
The loops use i <= n1 and j <= n2 which causes index to go out of bounds since arrays are 0-based.Step 2: Correct loop condition
Loops should use i < n1 and j < n2 to avoid accessing invalid memory.Final Answer:
The loops copying to L and R arrays use <= instead of < causing out-of-bounds access -> Option BQuick Check:
Loop bounds must be less than array size [OK]
- Using <= causes buffer overflow errors
- Confusing array size with last index
- Ignoring array indexing starting at 0
Solution
Step 1: Understand splitting linked list for Merge Sort
Using two pointers (slow and fast), where fast moves twice as fast, helps find the middle in one pass.Step 2: Compare other methods
Counting nodes requires two passes; breaking after first node is incorrect; converting to array is inefficient.Final Answer:
Use two pointers: move one pointer twice as fast as the other to find the middle -> Option DQuick Check:
Two-pointer technique = efficient split [OK]
- Counting nodes wastes time with two traversals
- Breaking after first node does not split evenly
- Converting to array loses linked list benefits
Ready to prove it?
Pass the test and this pattern moves unknown
