Count Inversions
Count Inversions
Category
Sorting
Difficulty
Hard
Problem Statement
Given an array of integers, count the number of inversions. An inversion is a pair of indices (i, j) such that i < j and array[i] > array[j]. In other words, count how many pairs are out of their natural sorted order.
Intuition
A brute-force approach checks all pairs in O(n^2). However, Merge Sort naturally encounters inversions during the merge step: when an element from the right half is chosen before remaining elements in the left half, each of those remaining left elements forms an inversion with it. By counting these during merge, we piggyback on Merge Sort's O(n log n) structure.
Approach
- Implement a modified Merge Sort that returns both the sorted array and the inversion count.
- Recursively sort and count inversions in the left half and right half.
- During the merge step, whenever an element from the right half is placed before remaining elements of the left half, add the number of remaining left elements to the inversion count (those are all "split inversions").
- The total inversions = left inversions + right inversions + split inversions.
Pseudocode
function countInversions(array):
if length(array) <= 1:
return array, 0
mid = length(array) / 2
left, leftInv = countInversions(array[0..mid])
right, rightInv = countInversions(array[mid..end])
merged, splitInv = mergeAndCount(left, right)
return merged, leftInv + rightInv + splitInv
function mergeAndCount(left, right):
result = []
inversions = 0
i = 0
j = 0
while i < length(left) and j < length(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
inversions += length(left) - i
j += 1
// append remaining elements from left and right
result.extend(left[i..])
result.extend(right[j..])
return result, inversions
Time & Space Complexity
- Time: O(n log n) — same as Merge Sort; the counting is done within the merge step at no additional asymptotic cost.
- Space: O(n) — for the auxiliary arrays used during merging, plus O(log n) recursion stack.
Key Insights
- When
right[j] < left[i], all elementsleft[i..end]form inversions withright[j]because the left half is already sorted internally. - The inversion count measures how far an array is from being sorted. A sorted array has 0 inversions; a reverse-sorted array has n*(n-1)/2 inversions (the maximum).
- This is a classic example of augmenting a divide-and-conquer algorithm with additional bookkeeping.
- Using
<=(not<) in the merge comparison ensures we do not count equal elements as inversions.
Edge Cases
- Sorted array: 0 inversions.
- Reverse-sorted array: n*(n-1)/2 inversions.
- Array with all identical elements: 0 inversions.
- Array of length 0 or 1: 0 inversions.
- Array with exactly one inversion: returns 1.