The Merge Sort pattern is a divide and conquer approach that divides a problem into smaller subproblems, solves them independently, and then merges the results.
It's characterized by its efficient merging step, which combines the solutions of subproblems to form the solution to the original problem.
Here's an implementation of the classic merge sort algorithm:
O(n log n) for all cases (best, average, worst), where n is the number of elements in the array.
O(n) for the auxiliary arrays used during the merge step.
The merge sort pattern can be adapted to count inversions in an array (pairs of elements that are out of order):
Learn the merge-based pattern of divide and conquer through the classic merge sort algorithm and its applications.
The Merge Sort pattern is a divide and conquer approach that divides a problem into smaller subproblems, solves them independently, and then merges the results.
It's characterized by its efficient merging step, which combines the solutions of subproblems to form the solution to the original problem.
Example:
Input: [38, 27, 43, 3, 9, 82, 10]
Divide: [38, 27, 43, 3] and [9, 82, 10]
Conquer: Sort each half recursively
Combine: Merge [3, 27, 38, 43] and [9, 10, 82] to get [3, 9, 10, 27, 38, 43, 82]
Preserves the relative order of equal elements, making it suitable for multi-level sorting.
O(n log n) time complexity in all cases, regardless of the input distribution.
Requires additional O(n) space for the merge step, making it less memory-efficient than some other sorting algorithms.
The divide and conquer nature makes it suitable for parallel processing, as subproblems can be solved independently.
Split the array into two halves. If the array has an odd number of elements, one half will have one more element than the other.
Recursively sort both halves. If the array has only one element, it's already sorted (base case).
Merge the two sorted halves to produce a single sorted array. This is done by comparing elements from both halves and adding the smaller one to the result.
Here's an implementation of the classic merge sort algorithm:
1234567891011121314151617181920212223242526272829303132333435363738function mergeSort(arr) { // Base case: arrays with 0 or 1 element are already sorted if (arr.length <= 1) { return arr; } // Divide: Split the array into two halves const mid = Math.floor(arr.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid); // Conquer: Recursively sort both halves const sortedLeft = mergeSort(left); const sortedRight = mergeSort(right); // Combine: Merge the sorted halves return merge(sortedLeft, sortedRight);} function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; // Compare elements from both arrays and add the smaller one to the result while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } // Add remaining elements return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));}
Key Insight: The merge function is the heart of the algorithm. It efficiently combines two sorted arrays into a single sorted array by comparing elements from both arrays and adding the smaller one to the result.
O(n log n) for all cases (best, average, worst), where n is the number of elements in the array.
Explanation: The array is divided log n times (until each subarray has 1 element), and each level of division requires O(n) work to merge the subarrays.
O(n) for the auxiliary arrays used during the merge step.
Explanation: The merge function creates temporary arrays to store the merged result, requiring O(n) extra space. The recursion stack also uses O(log n) space, but the O(n) term dominates.
The merge sort pattern can be adapted to count inversions in an array. An inversion is a pair of elements that are out of order (i.e., a[i] > a[j] where i < j).
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849function countInversions(arr) { // Base case: arrays with 0 or 1 element have 0 inversions if (arr.length <= 1) { return { sortedArray: arr, inversions: 0 }; } // Divide: Split the array into two halves const mid = Math.floor(arr.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid); // Conquer: Recursively count inversions in both halves const leftResult = countInversions(left); const rightResult = countInversions(right); // Combine: Merge the sorted halves and count split inversions const mergeResult = mergeAndCount(leftResult.sortedArray, rightResult.sortedArray); // Total inversions = inversions in left + inversions in right + split inversions return { sortedArray: mergeResult.sortedArray, inversions: leftResult.inversions + rightResult.inversions + mergeResult.inversions };} function mergeAndCount(left, right) { let sortedArray = []; let inversions = 0; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] <= right[rightIndex]) { sortedArray.push(left[leftIndex]); leftIndex++; } else { // If left[leftIndex] > right[rightIndex], it forms inversions with all // remaining elements in the left array sortedArray.push(right[rightIndex]); inversions += (left.length - leftIndex); rightIndex++; } } // Add remaining elements sortedArray = sortedArray.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); return { sortedArray, inversions };}
How it works: When merging two sorted arrays, if an element from the right array is smaller than an element from the left array, it forms inversions with all remaining elements in the left array. By counting these occurrences during the merge step, we can count all inversions in the array in O(n log n) time.
The Merge Sort pattern is a divide and conquer approach that divides a problem into smaller subproblems, solves them independently, and then merges the results.
It's characterized by its efficient merging step, which combines the solutions of subproblems to form the solution to the original problem.
Here's an implementation of the classic merge sort algorithm:
O(n log n) for all cases (best, average, worst), where n is the number of elements in the array.
O(n) for the auxiliary arrays used during the merge step.
The merge sort pattern can be adapted to count inversions in an array (pairs of elements that are out of order):
Learn the merge-based pattern of divide and conquer through the classic merge sort algorithm and its applications.
The Merge Sort pattern is a divide and conquer approach that divides a problem into smaller subproblems, solves them independently, and then merges the results.
It's characterized by its efficient merging step, which combines the solutions of subproblems to form the solution to the original problem.
Example:
Input: [38, 27, 43, 3, 9, 82, 10]
Divide: [38, 27, 43, 3] and [9, 82, 10]
Conquer: Sort each half recursively
Combine: Merge [3, 27, 38, 43] and [9, 10, 82] to get [3, 9, 10, 27, 38, 43, 82]
Preserves the relative order of equal elements, making it suitable for multi-level sorting.
O(n log n) time complexity in all cases, regardless of the input distribution.
Requires additional O(n) space for the merge step, making it less memory-efficient than some other sorting algorithms.
The divide and conquer nature makes it suitable for parallel processing, as subproblems can be solved independently.
Split the array into two halves. If the array has an odd number of elements, one half will have one more element than the other.
Recursively sort both halves. If the array has only one element, it's already sorted (base case).
Merge the two sorted halves to produce a single sorted array. This is done by comparing elements from both halves and adding the smaller one to the result.
Here's an implementation of the classic merge sort algorithm:
1234567891011121314151617181920212223242526272829303132333435363738function mergeSort(arr) { // Base case: arrays with 0 or 1 element are already sorted if (arr.length <= 1) { return arr; } // Divide: Split the array into two halves const mid = Math.floor(arr.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid); // Conquer: Recursively sort both halves const sortedLeft = mergeSort(left); const sortedRight = mergeSort(right); // Combine: Merge the sorted halves return merge(sortedLeft, sortedRight);} function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; // Compare elements from both arrays and add the smaller one to the result while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } // Add remaining elements return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));}
Key Insight: The merge function is the heart of the algorithm. It efficiently combines two sorted arrays into a single sorted array by comparing elements from both arrays and adding the smaller one to the result.
O(n log n) for all cases (best, average, worst), where n is the number of elements in the array.
Explanation: The array is divided log n times (until each subarray has 1 element), and each level of division requires O(n) work to merge the subarrays.
O(n) for the auxiliary arrays used during the merge step.
Explanation: The merge function creates temporary arrays to store the merged result, requiring O(n) extra space. The recursion stack also uses O(log n) space, but the O(n) term dominates.
The merge sort pattern can be adapted to count inversions in an array. An inversion is a pair of elements that are out of order (i.e., a[i] > a[j] where i < j).
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849function countInversions(arr) { // Base case: arrays with 0 or 1 element have 0 inversions if (arr.length <= 1) { return { sortedArray: arr, inversions: 0 }; } // Divide: Split the array into two halves const mid = Math.floor(arr.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid); // Conquer: Recursively count inversions in both halves const leftResult = countInversions(left); const rightResult = countInversions(right); // Combine: Merge the sorted halves and count split inversions const mergeResult = mergeAndCount(leftResult.sortedArray, rightResult.sortedArray); // Total inversions = inversions in left + inversions in right + split inversions return { sortedArray: mergeResult.sortedArray, inversions: leftResult.inversions + rightResult.inversions + mergeResult.inversions };} function mergeAndCount(left, right) { let sortedArray = []; let inversions = 0; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] <= right[rightIndex]) { sortedArray.push(left[leftIndex]); leftIndex++; } else { // If left[leftIndex] > right[rightIndex], it forms inversions with all // remaining elements in the left array sortedArray.push(right[rightIndex]); inversions += (left.length - leftIndex); rightIndex++; } } // Add remaining elements sortedArray = sortedArray.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); return { sortedArray, inversions };}
How it works: When merging two sorted arrays, if an element from the right array is smaller than an element from the left array, it forms inversions with all remaining elements in the left array. By counting these occurrences during the merge step, we can count all inversions in the array in O(n log n) time.