Homeβ€Ί DSAβ€Ί Counting Inversions in an Array using Merge Sort

Counting Inversions in an Array using Merge Sort

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Recursion β†’ Topic 6 of 9
Count inversions in an array using a modified merge sort in O(n log n).
βš™οΈ Intermediate β€” basic DSA knowledge assumed
In this tutorial, you'll learn:
  • When right[j] is taken before all of left[i..end] during merge, it forms inversions with every remaining left element. inversions += len(left) - i is the O(1) count.
  • Same O(n log n) as merge sort β€” inversion counting adds O(1) work per comparison, no asymptotic cost.
  • Inversion count = minimum number of adjacent swaps to sort the array. Every adjacent swap reduces the count by exactly 1.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
An inversion is a pair of positions (i, j) where i < j but arr[i] > arr[j] β€” an out-of-order pair. A sorted array has 0 inversions. A reverse-sorted array has n(n-1)/2 inversions. Counting inversions measures how 'unsorted' an array is. Merge sort counts them for free during the merge step β€” when you take an element from the right half before the left half, every remaining element in the left half forms an inversion with it.

Counting inversions is a classic application of the principle that divide and conquer can gather extra information during its combine step at no asymptotic cost. The merge step of merge sort compares elements β€” by counting how many left-array elements each right-array element 'jumps over', you get the inversion count for free.

This pattern β€” extracting additional information during a standard D&C operation β€” appears throughout algorithm design. Counting inversions for collaborative filtering, computing closest pair distance during a geometric divide, finding maximum subarray during merge. The lesson is: when you already have a D&C structure, ask what else you can compute in the combine step.

What is an Inversion?

A pair (i, j) is an inversion if i < j but arr[i] > arr[j].

Example: arr = [2, 4, 1, 3, 5] Inversions: (2,1), (4,1), (4,3) β†’ count = 3

Applications: - Measuring how similar two preference rankings are - Minimum swaps to sort an array = number of inversions - Kendall tau distance between two permutations

Modified Merge Sort

During the merge step of merge sort, when we pick an element from the right subarray before exhausting the left subarray, every remaining element in the left subarray forms an inversion with it.

count_inversions.py Β· PYTHON
12345678910111213141516171819202122232425262728293031
def count_inversions(arr: list) -> tuple[list, int]:
    """Returns (sorted_array, inversion_count)"""
    if len(arr) <= 1:
        return arr, 0
    mid = len(arr) // 2
    left,  left_inv  = count_inversions(arr[:mid])
    right, right_inv = count_inversions(arr[mid:])
    merged, split_inv = merge_count(left, right)
    return merged, left_inv + right_inv + split_inv

def merge_count(left: list, right: list) -> tuple[list, int]:
    merged, inversions = [], 0
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            merged.append(left[i])
            i += 1
        else:
            # left[i..] all form inversions with right[j]
            inversions += len(left) - i
            merged.append(right[j])
            j += 1
    merged.extend(left[i:])
    merged.extend(right[j:])
    return merged, inversions

_, count = count_inversions([2, 4, 1, 3, 5])
print(f'Inversions: {count}')  # 3

_, count = count_inversions([5, 4, 3, 2, 1])
print(f'Inversions in reverse: {count}')  # 10
β–Ά Output
Inversions: 3
Inversions in reverse: 10

Why This Works

When merging two sorted halves, any element right[j] that is taken before left[i] forms inversions with all of left[i..end]. This is because: 1. left is sorted β†’ all remaining elements left[i..] are β‰₯ left[i] > right[j] 2. All these elements come before right[j] in the original array (they're in the left half)

So inversions += len(left) - i counts them all in O(1).

πŸ”₯
ComplexityO(n log n) β€” same as merge sort. The inversion counting adds only O(1) work per comparison in the merge step.

Application β€” Minimum Swaps to Sort

The minimum number of adjacent swaps needed to sort an array equals the number of inversions. Each adjacent swap reduces the inversion count by exactly 1.

min_swaps.py Β· PYTHON
12345
def min_swaps_to_sort(arr):
    _, count = count_inversions(arr)
    return count

print(min_swaps_to_sort([3, 1, 2]))  # 2 swaps: [3,1,2]β†’[1,3,2]β†’[1,2,3]
β–Ά Output
2

🎯 Key Takeaways

  • When right[j] is taken before all of left[i..end] during merge, it forms inversions with every remaining left element. inversions += len(left) - i is the O(1) count.
  • Same O(n log n) as merge sort β€” inversion counting adds O(1) work per comparison, no asymptotic cost.
  • Inversion count = minimum number of adjacent swaps to sort the array. Every adjacent swap reduces the count by exactly 1.
  • Applications: Kendall tau distance between two rankings (collaborative filtering similarity), measuring how 'sorted' external data is before choosing a sort algorithm.
  • O(n log n) is optimal β€” lower bound by reduction from sorting. The merge sort approach achieves this bound.

Interview Questions on This Topic

  • QHow does the merge step naturally count inversions?
  • QWhat is the inversion count of a reverse-sorted array of n elements?
  • QWhy is O(n log n) achievable β€” what's the lower bound for counting inversions?
  • QHow would you use inversion count to measure similarity between two rankings?

Frequently Asked Questions

Can we count inversions in O(n)?

No β€” counting inversions has a lower bound of Ξ©(n log n) by reduction from sorting. The merge sort approach is therefore optimal.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousDivide and Conquer β€” Strategy and PatternsNext β†’Closest Pair of Points β€” Divide and Conquer
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged