Facebook Pixel

493. Reverse Pairs

Problem Description

You are given an integer array nums. Your task is to count how many reverse pairs exist in the array.

A reverse pair is defined as a pair of indices (i, j) that satisfies both of these conditions:

  • The first index comes before the second index: 0 <= i < j < nums.length
  • The element at position i is more than twice the element at position j: nums[i] > 2 * nums[j]

For example, if nums = [1, 3, 2, 3, 1], the reverse pairs would be:

  • (1, 4) because nums[1] = 3 > 2 * nums[4] = 2 * 1 = 2
  • (3, 4) because nums[3] = 3 > 2 * nums[4] = 2 * 1 = 2

So the answer would be 2.

The solution uses a modified merge sort algorithm. During the merge process, it counts reverse pairs by comparing elements from the left half with twice the elements from the right half. The key insight is that if nums[i] > 2 * nums[j] for some i in the left half and j in the right half, then all elements from i to the end of the left half will also form reverse pairs with nums[j] (since the arrays are sorted). This allows efficient counting of reverse pairs while maintaining the overall O(n log n) time complexity of merge sort.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The naive approach would be to check every pair (i, j) where i < j and count how many satisfy nums[i] > 2 * nums[j]. This would take O(n²) time, which is inefficient for large arrays.

The key observation is that this problem is similar to counting inversions in an array, but with a modified condition. In the classic inversion problem, we count pairs where nums[i] > nums[j] and i < j. Here, we need nums[i] > 2 * nums[j].

Why does merge sort work well here? During merge sort, we divide the array into two halves and recursively sort them. At each merge step, we have two sorted subarrays. The crucial insight is that for any element in the left subarray and any element in the right subarray, we naturally have i < j (left indices come before right indices).

When we have two sorted halves, we can efficiently count reverse pairs. If we find that nums[i] > 2 * nums[j] for some position i in the left half and position j in the right half, then due to the sorted property, all elements from position i to the end of the left half will also satisfy this condition with nums[j]. This means we can add (mid - i + 1) pairs at once, rather than checking each one individually.

The beauty of this approach is that we can count the reverse pairs during the merge process without adding extra time complexity. We perform two passes during each merge:

  1. First pass: Count reverse pairs by comparing nums[i] with 2 * nums[j]
  2. Second pass: Actually merge the two sorted halves to maintain the sorted property for the next level

This way, we leverage the divide-and-conquer nature of merge sort to reduce the problem from O(n²) to O(n log n).

Solution Approach

The solution implements a modified merge sort algorithm to efficiently count reverse pairs. Let's walk through the implementation step by step:

Main Structure: The solution uses a recursive merge_sort function that takes left and right boundaries (l, r) of the current subarray being processed.

Base Case:

if l >= r:
    return 0

When the subarray has one or zero elements, there are no pairs to count.

Divide Step:

mid = (l + r) >> 1
ans = merge_sort(l, mid) + merge_sort(mid + 1, r)

We divide the array at the midpoint and recursively count reverse pairs in both halves. The bit shift operation >> 1 is equivalent to integer division by 2.

Count Reverse Pairs:

i, j = l, mid + 1
while i <= mid and j <= r:
    if nums[i] <= 2 * nums[j]:
        i += 1
    else:
        ans += mid - i + 1
        j += 1

This is the crucial counting phase. We use two pointers:

  • i traverses the left sorted half [l, mid]
  • j traverses the right sorted half [mid + 1, r]

When nums[i] > 2 * nums[j], all elements from index i to mid in the left half will form reverse pairs with nums[j] (since both halves are sorted). So we add (mid - i + 1) to our count and move j forward.

Merge Phase:

t = []
i, j = l, mid + 1
while i <= mid and j <= r:
    if nums[i] <= nums[j]:
        t.append(nums[i])
        i += 1
    else:
        t.append(nums[j])
        j += 1

After counting, we perform the standard merge operation to combine the two sorted halves into a single sorted array. We use a temporary array t to store the merged result.

Handle Remaining Elements:

t.extend(nums[i : mid + 1])
t.extend(nums[j : r + 1])
nums[l : r + 1] = t

We append any remaining elements from either half to the temporary array, then copy the sorted result back to the original array.

Time and Space Complexity:

  • Time: O(n log n) - We have log n levels of recursion, and at each level, we process all n elements once for counting and once for merging.
  • Space: O(n) - For the temporary array used during merging and the recursion stack.

The elegance of this solution lies in leveraging the sorted property of subarrays during merge sort to count reverse pairs in linear time at each level, avoiding the need for nested loops.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with nums = [2, 4, 3, 5, 1].

Initial Call: merge_sort(0, 4) on [2, 4, 3, 5, 1]

Level 1 - First Split:

  • Calculate mid = 2
  • Recursively call merge_sort(0, 2) for [2, 4, 3]
  • Recursively call merge_sort(3, 4) for [5, 1]

Level 2 - Left Branch [2, 4, 3]:

  • Split into [2, 4] and [3]
  • After recursive calls, we have sorted subarrays: [2, 4] and [3]
  • Count phase: Compare elements from left [2, 4] with twice the elements from right [3]
    • i=0, j=0: Is 2 > 2*3? No (2 ≤ 6), move i forward
    • i=1, j=0: Is 4 > 2*3? No (4 ≤ 6), move i forward
    • No reverse pairs found
  • Merge phase: Merge to get [2, 3, 4]

Level 2 - Right Branch [5, 1]:

  • Split into [5] and [1]
  • Count phase: Compare [5] with [1]
    • i=0, j=0: Is 5 > 2*1? Yes (5 > 2), add 1 pair
  • Merge phase: Merge to get [1, 5]

Level 1 - Final Merge: Now we merge [2, 3, 4] and [1, 5]

  • Count phase: Find reverse pairs between left and right halves
    • i=0, j=0: Is 2 > 2*1? No (2 ≤ 2), move i forward
    • i=1, j=0: Is 3 > 2*1? Yes (3 > 2), add (2-1+1) = 2 pairs
      • This counts pairs (1,3) and (2,3) where indices refer to original positions
    • j=1: Is 3 > 2*5? No (3 ≤ 10), move i forward
    • Continue until both pointers reach the end
  • Merge phase: Merge to get [1, 2, 3, 4, 5]

Total reverse pairs found: 0 + 1 + 2 = 3

The three reverse pairs in the original array are:

  1. (1, 4): nums[1]=4 > 2*nums[4]=2
  2. (2, 4): nums[2]=3 > 2*nums[4]=2
  3. (3, 4): nums[3]=5 > 2*nums[4]=2

The algorithm efficiently counts these pairs by recognizing that once we find one element in the left sorted half that satisfies the condition, all subsequent elements will too.

Solution Implementation

1class Solution:
2    def reversePairs(self, nums: List[int]) -> int:
3        """
4        Count reverse pairs where i < j and nums[i] > 2 * nums[j]
5        using modified merge sort algorithm.
6      
7        Args:
8            nums: List of integers
9          
10        Returns:
11            Count of reverse pairs
12        """
13      
14        def merge_sort_and_count(left: int, right: int) -> int:
15            """
16            Recursively divide array and count reverse pairs during merge.
17          
18            Args:
19                left: Left boundary index (inclusive)
20                right: Right boundary index (inclusive)
21              
22            Returns:
23                Count of reverse pairs in range [left, right]
24            """
25            # Base case: single element or invalid range
26            if left >= right:
27                return 0
28          
29            # Divide the array into two halves
30            mid = (left + right) // 2
31          
32            # Recursively count pairs in left half, right half, and across halves
33            count = merge_sort_and_count(left, mid) + merge_sort_and_count(mid + 1, right)
34          
35            # Count reverse pairs across the two sorted halves
36            # For each element in left half, count elements in right half
37            # where nums[i] > 2 * nums[j]
38            i, j = left, mid + 1
39            while i <= mid and j <= right:
40                if nums[i] <= 2 * nums[j]:
41                    i += 1
42                else:
43                    # All elements from i to mid satisfy the condition
44                    # with current nums[j]
45                    count += mid - i + 1
46                    j += 1
47          
48            # Merge the two sorted halves
49            temp = []
50            i, j = left, mid + 1
51          
52            # Merge elements in sorted order
53            while i <= mid and j <= right:
54                if nums[i] <= nums[j]:
55                    temp.append(nums[i])
56                    i += 1
57                else:
58                    temp.append(nums[j])
59                    j += 1
60          
61            # Add remaining elements from left half
62            temp.extend(nums[i:mid + 1])
63          
64            # Add remaining elements from right half
65            temp.extend(nums[j:right + 1])
66          
67            # Copy sorted elements back to original array
68            nums[left:right + 1] = temp
69          
70            return count
71      
72        # Start merge sort from entire array
73        return merge_sort_and_count(0, len(nums) - 1)
74
1class Solution {
2    private int[] nums;
3    private int[] tempArray;  // Temporary array for merge operation
4
5    /**
6     * Counts the number of reverse pairs in the array.
7     * A reverse pair is defined as (i, j) where i < j and nums[i] > 2 * nums[j].
8     * 
9     * @param nums Input array
10     * @return Number of reverse pairs
11     */
12    public int reversePairs(int[] nums) {
13        this.nums = nums;
14        int length = nums.length;
15        this.tempArray = new int[length];
16      
17        // Perform merge sort and count reverse pairs simultaneously
18        return mergeSort(0, length - 1);
19    }
20
21    /**
22     * Recursive merge sort that counts reverse pairs.
23     * 
24     * @param left Left boundary index (inclusive)
25     * @param right Right boundary index (inclusive)
26     * @return Number of reverse pairs in the range [left, right]
27     */
28    private int mergeSort(int left, int right) {
29        // Base case: single element or invalid range
30        if (left >= right) {
31            return 0;
32        }
33      
34        // Calculate middle point
35        int mid = (left + right) >> 1;
36      
37        // Recursively count reverse pairs in left and right halves
38        int reversePairCount = mergeSort(left, mid) + mergeSort(mid + 1, right);
39      
40        // Count reverse pairs between left and right halves
41        // Using two pointers to find pairs where nums[i] > 2 * nums[j]
42        int i = left;
43        int j = mid + 1;
44        while (i <= mid && j <= right) {
45            // Use long to avoid integer overflow when multiplying by 2
46            if (nums[i] <= nums[j] * 2L) {
47                i++;
48            } else {
49                // All elements from i to mid form reverse pairs with nums[j]
50                reversePairCount += mid - i + 1;
51                j++;
52            }
53        }
54      
55        // Merge the two sorted halves
56        i = left;
57        j = mid + 1;
58        int tempIndex = 0;
59      
60        // Merge elements in sorted order
61        while (i <= mid && j <= right) {
62            if (nums[i] <= nums[j]) {
63                tempArray[tempIndex++] = nums[i++];
64            } else {
65                tempArray[tempIndex++] = nums[j++];
66            }
67        }
68      
69        // Copy remaining elements from left half
70        while (i <= mid) {
71            tempArray[tempIndex++] = nums[i++];
72        }
73      
74        // Copy remaining elements from right half
75        while (j <= right) {
76            tempArray[tempIndex++] = nums[j++];
77        }
78      
79        // Copy merged elements back to original array
80        for (i = left; i <= right; i++) {
81            nums[i] = tempArray[i - left];
82        }
83      
84        return reversePairCount;
85    }
86}
87
1class Solution {
2public:
3    int reversePairs(vector<int>& nums) {
4        int n = nums.size();
5        // Temporary array for merge sort
6        vector<int> temp(n);
7      
8        // Lambda function for merge sort with reverse pairs counting
9        function<int(int, int)> mergeSort = [&](int left, int right) -> int {
10            // Base case: single element or invalid range
11            if (left >= right) {
12                return 0;
13            }
14          
15            // Calculate middle point
16            int mid = (left + right) >> 1;
17          
18            // Recursively count reverse pairs in left and right halves
19            int count = mergeSort(left, mid) + mergeSort(mid + 1, right);
20          
21            // Count reverse pairs across the two halves
22            // A reverse pair is when nums[i] > 2 * nums[j] where i < j
23            int i = left;
24            int j = mid + 1;
25            while (i <= mid && j <= right) {
26                // Use long long to prevent overflow when multiplying by 2
27                if (nums[i] <= 2LL * nums[j]) {
28                    i++;
29                } else {
30                    // All elements from i to mid form reverse pairs with nums[j]
31                    count += mid - i + 1;
32                    j++;
33                }
34            }
35          
36            // Merge the two sorted halves
37            i = left;
38            j = mid + 1;
39            int k = 0;
40          
41            // Merge elements in sorted order
42            while (i <= mid && j <= right) {
43                if (nums[i] <= nums[j]) {
44                    temp[k++] = nums[i++];
45                } else {
46                    temp[k++] = nums[j++];
47                }
48            }
49          
50            // Copy remaining elements from left half
51            while (i <= mid) {
52                temp[k++] = nums[i++];
53            }
54          
55            // Copy remaining elements from right half
56            while (j <= right) {
57                temp[k++] = nums[j++];
58            }
59          
60            // Copy sorted elements back to original array
61            for (i = left; i <= right; i++) {
62                nums[i] = temp[i - left];
63            }
64          
65            return count;
66        };
67      
68        // Start merge sort from the entire array
69        return mergeSort(0, n - 1);
70    }
71};
72
1function reversePairs(nums: number[]): number {
2    const n = nums.length;
3    // Temporary array for merge sort
4    const temp: number[] = new Array(n);
5  
6    // Recursive merge sort function with reverse pairs counting
7    const mergeSort = (left: number, right: number): number => {
8        // Base case: single element or invalid range
9        if (left >= right) {
10            return 0;
11        }
12      
13        // Calculate middle point using bit shift for efficiency
14        const mid = (left + right) >> 1;
15      
16        // Recursively count reverse pairs in left and right halves
17        let count = mergeSort(left, mid) + mergeSort(mid + 1, right);
18      
19        // Count reverse pairs across the two halves
20        // A reverse pair is when nums[i] > 2 * nums[j] where i < j
21        let i = left;
22        let j = mid + 1;
23        while (i <= mid && j <= right) {
24            // Check if current element forms a reverse pair
25            // Cast to ensure no overflow when multiplying by 2
26            if (nums[i] <= 2 * nums[j]) {
27                i++;
28            } else {
29                // All elements from i to mid form reverse pairs with nums[j]
30                count += mid - i + 1;
31                j++;
32            }
33        }
34      
35        // Merge the two sorted halves
36        i = left;
37        j = mid + 1;
38        let k = 0;
39      
40        // Merge elements in sorted order into temporary array
41        while (i <= mid && j <= right) {
42            if (nums[i] <= nums[j]) {
43                temp[k++] = nums[i++];
44            } else {
45                temp[k++] = nums[j++];
46            }
47        }
48      
49        // Copy remaining elements from left half
50        while (i <= mid) {
51            temp[k++] = nums[i++];
52        }
53      
54        // Copy remaining elements from right half
55        while (j <= right) {
56            temp[k++] = nums[j++];
57        }
58      
59        // Copy sorted elements back to original array
60        for (i = left; i <= right; i++) {
61            nums[i] = temp[i - left];
62        }
63      
64        return count;
65    };
66  
67    // Start merge sort from the entire array
68    return mergeSort(0, n - 1);
69}
70

Time and Space Complexity

Time Complexity: O(n log n)

The algorithm uses a modified merge sort approach. The merge sort divides the array into two halves recursively until single elements are reached, creating log n levels in the recursion tree. At each level:

  • The first while loop counts reverse pairs by comparing elements from left and right subarrays, taking O(n) time in total across all recursive calls at that level
  • The second while loop performs the standard merge operation, also taking O(n) time
  • The extend operations take O(n) time in worst case

Since we have log n levels and each level does O(n) work, the overall time complexity is O(n log n).

Space Complexity: O(n)

The space complexity consists of:

  • The temporary array t used during merging, which can hold at most n elements across all recursive calls at the same level: O(n)
  • The recursion call stack depth, which is O(log n) for the divide-and-conquer approach
  • Since O(n) + O(log n) = O(n), the overall space complexity is O(n)

Common Pitfalls

1. Integer Overflow When Doubling Values

The Problem: When calculating 2 * nums[j], integer overflow can occur if nums[j] is very large. In Python, this isn't typically an issue due to arbitrary precision integers, but in languages like Java or C++, this can cause incorrect comparisons.

For example, if nums[j] = 2^30, then 2 * nums[j] = 2^31 which overflows a 32-bit signed integer, potentially wrapping to a negative value and causing incorrect pair counting.

Solution: Instead of comparing nums[i] > 2 * nums[j], rearrange the comparison to avoid multiplication:

# Instead of:
if nums[i] > 2 * nums[j]:
  
# Use:
if nums[i] / 2.0 > nums[j]:

Or use long/64-bit integers in languages with fixed integer sizes:

# For languages with overflow concerns:
if nums[i] > 2 * long(nums[j]):

2. Off-by-One Errors in Index Management

The Problem: The algorithm uses inclusive boundaries [left, right] which can lead to confusion. Common mistakes include:

  • Using mid instead of mid + 1 when starting the right half
  • Incorrect range calculations when counting pairs
  • Wrong slice indices when copying back sorted elements

Solution: Be consistent with boundary conventions and carefully track indices:

# Ensure correct boundaries:
# Left half: [left, mid] (inclusive)
# Right half: [mid + 1, right] (inclusive)

# When counting reverse pairs:
count += mid - i + 1  # "+1" because mid is inclusive

# When extending remaining elements:
temp.extend(nums[i:mid + 1])      # mid + 1 for inclusive mid
temp.extend(nums[j:right + 1])    # right + 1 for inclusive right

3. Modifying the Counting Logic During Merge

The Problem: A common mistake is trying to count reverse pairs during the actual merge phase instead of in a separate pass. This leads to incorrect counts because elements get moved and the relative positions change.

# WRONG: Trying to count during merge
while i <= mid and j <= right:
    if nums[i] <= nums[j]:
        temp.append(nums[i])
        i += 1
    else:
        # Don't count here! Elements are being reordered
        if nums[i] > 2 * nums[j]:  # This is wrong timing
            count += ...
        temp.append(nums[j])
        j += 1

Solution: Always count reverse pairs in a separate pass before the merge operation:

# CORRECT: Count first, then merge
# First pass: count reverse pairs
i, j = left, mid + 1
while i <= mid and j <= right:
    if nums[i] <= 2 * nums[j]:
        i += 1
    else:
        count += mid - i + 1
        j += 1

# Second pass: merge the arrays
i, j = left, mid + 1
# ... merge logic ...

4. Not Resetting Pointers Between Counting and Merging

The Problem: After counting reverse pairs, the pointers i and j have moved. Forgetting to reset them before the merge phase causes incorrect merging.

Solution: Always reinitialize pointers before the merge phase:

# After counting phase
i, j = left, mid + 1  # Reset pointers
# Now perform merge

5. Incorrect Base Case Handling

The Problem: Using if left > right: instead of if left >= right: in the base case. When left == right, we have a single element which cannot form any pairs, so we should return 0.

Solution: Use the correct base case:

if left >= right:  # Handles both empty and single-element cases
    return 0
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More