Merge Sort Fundamentals
Merge sort is a divide-and-conquer algorithm that divides the array into halves, recursively sorts them, then merges the sorted halves.
Algorithm Steps
- Divide: Split array into two halves
- Conquer: Recursively sort each half
- Combine: Merge the two sorted halves
Visual Example
Original: [38, 27, 43, 3, 9, 82, 10]
Divide Phase:
[38, 27, 43, 3] → [38, 27] [43, 3] → [38] [27] [43] [3]
[9, 82, 10] → [9, 82] [10] → [9] [82] [10]
Merge Phase:
[27, 38] [3, 43] → [3, 27, 38, 43]
[9, 82] [10] → [9, 10, 82]
Final Merge:
[3, 27, 38, 43] [9, 10, 82] → [3, 9, 10, 27, 38, 43, 82]
Java Implementation
public void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// Sort first and second halves
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Merge sorted halves
merge(arr, left, mid, right);
}
}
private void merge(int[] arr, int left, int mid, int right) {
// Create temporary arrays
int[] leftArr = Arrays.copyOfRange(arr, left, mid + 1);
int[] rightArr = Arrays.copyOfRange(arr, mid + 1, right + 1);
int i = 0, j = 0, k = left;
// Merge elements in sorted order
while (i < leftArr.length && j < rightArr.length) {
if (leftArr[i] <= rightArr[j]) {
arr[k++] = leftArr[i++];
} else {
arr[k++] = rightArr[j++];
}
}
// Copy remaining elements
while (i < leftArr.length) {
arr[k++] = leftArr[i++];
}
while (j < rightArr.length) {
arr[k++] = rightArr[j++];
}
}
In-Place Merge Sort (Optimized)
public void mergeSortInPlace(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSortInPlace(arr, left, mid);
mergeSortInPlace(arr, mid + 1, right);
mergeInPlace(arr, left, mid, right);
}
}
private void mergeInPlace(int[] arr, int left, int mid, int right) {
int start2 = mid + 1;
// If already sorted, no need to merge
if (arr[mid] <= arr[start2]) return;
while (left <= mid && start2 <= right) {
if (arr[left] <= arr[start2]) {
left++;
} else {
int value = arr[start2];
int index = start2;
// Shift elements right
while (index != left) {
arr[index] = arr[index - 1];
index--;
}
arr[left] = value;
left++;
mid++;
start2++;
}
}
}
Complexity Analysis
| Aspect | Complexity |
|---|---|
| Time (all cases) | O(n log n) |
| Space | O(n) - temporary arrays |
| Stable | Yes - preserves order of equal elements |
Merge Sort Applications
Merge Sort for Linked Lists
Merge sort is ideal for linked lists due to:
- No random access needed (unlike quicksort)
- O(1) merge (just relink pointers)
- O(log n) space for recursion stack only
public ListNode mergeSortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// Find middle using slow/fast pointers
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next;
slow.next = null; // Split list
// Recursively sort halves
ListNode left = mergeSortList(head);
ListNode right = mergeSortList(mid);
// Merge sorted lists
return mergeLists(left, right);
}
private ListNode mergeLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
current.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
External Merge Sort
Used for sorting files larger than memory:
- Divide file into chunks that fit in memory
- Sort each chunk in memory
- Merge sorted chunks using k-way merge
When to Use Merge Sort
Use when:
- Guaranteed O(n log n) needed
- Stability is required
- Sorting linked lists
- External sorting (large files)
Avoid when:
- Memory is limited (use quicksort in-place)
- Array is nearly sorted (use insertion sort)
- Stability not needed (quicksort may be faster in practice)
Comparison with Other Sorts
| Algorithm | Time (avg) | Time (worst) | Space | Stable |
|---|---|---|---|---|
| Merge Sort | O(n log n) | O(n log n) | O(n) | Yes |
| Quick Sort | O(n log n) | O(n²) | O(log n) | No |
| Heap Sort | O(n log n) | O(n log n) | O(1) | No |
Interactive Visualization
Merge Sort Split & Merge
Practice Problems
Given an array of k linked-lists sorted in ascending order, merge all into a single sorted list.
Example:
Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]
Merge three sorted lists into one sorted list.
Optimal Solution — O(N log k) time, O(log k) space
Divide and conquer - pairwise merge lists
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) return null;
return mergeLists(lists, 0, lists.length - 1);
}
private ListNode mergeLists(ListNode[] lists, int lo, int hi) {
if (lo == hi) return lists[lo];
int mid = lo + (hi - lo) / 2;
ListNode left = mergeLists(lists, lo, mid);
ListNode right = mergeLists(lists, mid + 1, hi);
return mergeTwo(left, right);
}
private ListNode mergeTwo(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val <= l2.val) { l1.next = mergeTwo(l1.next, l2); return l1; }
else { l2.next = mergeTwo(l1, l2.next); return l2; }
}
}Edge Cases:
- Empty list
- Single list
- All same values
Sort a linked list in O(n log n) time using merge sort.
Example:
Input: head = [4,2,1,3]
Output: [1,2,3,4]
Merge sort on linked list.
Optimal Solution — O(n log n) time, O(log n) space
Merge sort: find middle, recursively sort halves, merge
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next;
slow.next = null;
ListNode left = sortList(head);
ListNode right = sortList(mid);
return merge(left, right);
}
private ListNode merge(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val <= l2.val) { l1.next = merge(l1.next, l2); return l1; }
else { l2.next = merge(l1, l2.next); return l2; }
}
}Edge Cases:
- Empty list
- Single node
- Already sorted
- Reverse sorted
Given an integer array nums, return an integer array counts where counts[i] is the number of smaller elements to the right of nums[i].
Example:
Input: nums = [5,2,6,1]
Output: [2,1,1,0]
To the right of 5: 2,1 (2 smaller). To the right of 2: 1 (1 smaller).
Optimal Solution — O(n log n) time, O(n) space
Merge sort while counting inversions
class Solution {
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
Integer[] result = new Integer[n];
int[][] pairs = new int[n][2];
for (int i = 0; i < n; i++) { pairs[i][0] = nums[i]; pairs[i][1] = i; }
mergeSort(pairs, 0, n - 1, result);
return Arrays.asList(result);
}
private void mergeSort(int[][] pairs, int lo, int hi, Integer[] result) {
if (lo >= hi) return;
int mid = lo + (hi - lo) / 2;
mergeSort(pairs, lo, mid, result);
mergeSort(pairs, mid + 1, hi, result);
merge(pairs, lo, mid, hi, result);
}
private void merge(int[][] pairs, int lo, int mid, int hi, Integer[] result) {
int[][] temp = new int[hi - lo + 1][];
int i = lo, j = mid + 1, k = 0, count = 0;
while (i <= mid && j <= hi) {
if (pairs[i][0] <= pairs[j][0]) { result[pairs[i][1]] += count; temp[k++] = pairs[i++]; }
else { count++; temp[k++] = pairs[j++]; }
}
while (i <= mid) { result[pairs[i][1]] += count; temp[k++] = pairs[i++]; }
while (j <= hi) temp[k++] = pairs[j++];
System.arraycopy(temp, 0, pairs, lo, temp.length);
}
}Edge Cases:
- All same elements
- Strictly increasing
- Strictly decreasing
Quiz
1. What is the time complexity of merge sort?
2. Why is merge sort preferred for linked lists over quicksort?
3. What is the primary purpose of Merge Sort?
4. What is a common mistake when implementing Merge Sort?
Flashcards
Question
What are the three phases of merge sort?
Click to reveal answer
Answer
1. Divide: Split array into halves. 2. Conquer: Recursively sort halves. 3. Combine: Merge sorted halves.
Question
Why is merge sort stable?
Click to reveal answer
Answer
During merge step, when elements are equal, we take from left half first, preserving original order.
Question
What is Merge Sort?
Click to reveal answer
Answer
Merge Sort is a key concept in software engineering.
Question
When to use Merge Sort?
Click to reveal answer
Answer
Use Merge Sort when building production systems that require reliability, scalability, and maintainability.
Question
Merge Sort best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.Merge sort provides guaranteed O(n log n) performance
- 2.It is stable, preserving order of equal elements
- 3.Space complexity is O(n) due to temporary arrays
- 4.Ideal for linked lists where merge is O(1)
Interview Tips
- •Discuss time/space tradeoffs compared to quicksort
- •Explain why merge sort is preferred for linked lists
- •Mention stability requirement as a reason to choose merge sort
- •Be prepared to implement both array and linked list versions
Cheat Sheet
Merge Sort Cheat Sheet
Algorithm:
- Divide array into two halves
- Recursively sort each half
- Merge sorted halves
Complexity:
- Time: O(n log n) all cases
- Space: O(n) for temporary arrays
- Stable: Yes
Implementation:
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);
}
}
When to Use:
- Guaranteed O(n log n)
- Stability required
- Linked list sorting
- External sorting