AlgoMaster Logo

Reverse Pairs

hardFrequency7 min readUpdated June 23, 2026

Understanding the Problem

We need to count pairs of indices (i, j) where i comes before j in the array and the element at i is strictly greater than twice the element at j. This is a modified inversion counting problem. In a standard inversion, the condition is nums[i] > nums[j]. Here, the condition is asymmetric: nums[i] > 2 * nums[j].

What makes this harder than ordinary inversion counting is that the counting condition and the sorting condition differ. In regular inversion counting with merge sort, you count and sort using the same comparison. Here, you count pairs satisfying nums[i] > 2 * nums[j] but still sort by plain value order. The two operations have to be decoupled.

The values can be as large as 2^31 - 1, so 2 * nums[j] overflows a 32-bit integer. The comparison needs long (64-bit) arithmetic.

Key Constraints:

  • 1 <= nums.length <= 5 * 10^4. With n up to 50,000, an O(n^2) brute force runs about 1.25 billion comparisons, which is too slow. The target is O(n log n) or better.
  • -2^31 <= nums[i] <= 2^31 - 1. This is the full 32-bit integer range, so 2 * nums[j] overflows a 32-bit int and the comparison must use long. The range is also too wide to index a Binary Indexed Tree directly by value, which forces coordinate compression for that approach.

Approach 1: Brute Force

Intuition

Check every pair (i, j) with i < j and count the ones where nums[i] > 2 * nums[j]. Two nested loops, testing the condition for each pair.

This gives a baseline to confirm correctness before optimizing.

Algorithm

  1. Initialize a counter to 0.
  2. For each index i from 0 to n - 2, iterate through indices j from i + 1 to n - 1.
  3. If nums[i] > 2 * (long) nums[j], increment the counter. Use long to avoid overflow.
  4. Return the counter.

Example Walkthrough

1Initialize: count=0, start with i=0
0
i
1
1
3
j
2
2
3
3
4
1
1/6

Code

This is too slow for n up to 50,000. The next approach reuses the structure of merge sort to count valid pairs while it sorts, bringing the cost down to O(n log n).

Approach 2: Merge Sort (Divide and Conquer)

Intuition

During the merge step of merge sort, you hold two sorted halves, and every element in the left half originally appeared before every element in the right half. That positional guarantee matches the i < j requirement for a reverse pair: any pair we count with a left element before a right element automatically has the smaller index on the left.

The complication is that counting and sorting use different comparisons. In standard inversion counting both use nums[i] > nums[j]. For Reverse Pairs the counting condition is nums[i] > 2 * nums[j], while the merge still orders by plain value. The two cannot share a single pass, so we split the merge step into two phases:

  1. Count phase: Use two pointers on the two sorted halves to count reverse pairs between them.
  2. Merge phase: Do the standard merge to sort the elements for the next recursion level up.

Both phases run in O(n) time because both halves are sorted, so the overall cost stays O(n log n).

The count phase works as a single pass because the left half is sorted. As we advance to a larger left element, the right pointer never has to move backward: if left[i] > 2 * right[j], then left[i+1] >= left[i] > 2 * right[j], so every right element that paired with left[i] still pairs with left[i+1]. For each left element we extend the right pointer as far as left[i] > 2 * right[j] holds, and the count of valid right elements only grows.

Algorithm

  1. Recursively split the array into two halves until each subarray has one element.
  2. For each pair of sorted halves, do a counting pass: use two pointers to count pairs (i, j) where left[i] > 2 * right[j].
  3. Do the standard merge to combine the two sorted halves into one sorted array.
  4. Return the total count from all recursion levels.

Example Walkthrough

1Initial array, split into [1,3,2] and [3,1]
0
1
1
3
2
2
left half
3
3
4
1
right half
1/7

Code

Merge sort reaches the optimal O(n log n) but rearranges the input array. The next approach hits the same time bound with a Binary Indexed Tree and leaves the input untouched.

Approach 3: Binary Indexed Tree with Coordinate Compression

Intuition

Instead of sorting the array, process elements from right to left and maintain a count of the elements seen so far. For each element nums[i], count how many already-seen elements nums[j] (where j > i) satisfy nums[i] > 2 * nums[j]. Since nums[i] > 2 * nums[j] is equivalent to nums[j] < nums[i] / 2, and values are integers, the condition becomes nums[j] <= floor((nums[i] - 1) / 2). So each step is a "how many seen values are at most this threshold?" query.

A Binary Indexed Tree (BIT) answers that with point updates and prefix sum queries in O(log n) time. The value range spans the full 32-bit integer range, which is too large to index directly, so we apply coordinate compression: collect every value we will ever query or insert, sort and deduplicate them, and map each to a compact index.

Processing right to left is what enforces the i < j constraint: every element already in the BIT sits at a larger original index than the current one, so a value match is also a valid index pair.

Algorithm

  1. Collect all values: for each nums[i], add both nums[i] and floor((nums[i] - 1) / 2) (the threshold) to a list.
  2. Sort and deduplicate this list. Create a mapping from each value to its compressed index (1-indexed).
  3. Initialize a BIT of size equal to the number of unique values.
  4. Process elements from right to left. For each nums[i]:
    • Look up the compressed index of the threshold floor((nums[i] - 1) / 2).
    • Query the BIT for the prefix sum up to that index.
    • Insert nums[i] by updating the BIT at the compressed index of nums[i].
  5. Return the total count.

Example Walkthrough

nums
1Process right to left. Start at i=4, nums[4]=1
0
1
1
3
2
2
3
3
4
i
1
BIT (logical view)
1BIT empty, processing i=4
1/6

Code