101 Logo
onenoughtone

Merge Sort Pattern

What is the Merge Sort Pattern?

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.

Key Steps

  1. Divide: Split the input into smaller, manageable pieces
  2. Conquer: Solve each piece recursively
  3. Combine: Merge the solved pieces to form the solution to the original problem

Merge Sort Implementation

Here's an implementation of the classic merge sort algorithm:

function 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)); }

Time and Space Complexity

Time Complexity:

O(n log n) for all cases (best, average, worst), where n is the number of elements in the array.

Space Complexity:

O(n) for the auxiliary arrays used during the merge step.

Application: Counting Inversions

The merge sort pattern can be adapted to count inversions in an array (pairs of elements that are out of order):

function 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 }; }
IntroVisualizePatternsPractice
101 Logo
onenoughtone