Reverse Pairs (Leetcode)

Hero Image

Problem Overview

The LeetCode problem titled "Reverse Pairs" is categorized under arrays, which is a fundamental topic in data structures. The task in this problem is to count the number of reverse pairs in an array. A reverse pair is defined as a pair of indices (i, j) such that i < j and nums[i] > 2 * nums[j].

Understanding the Problem

Examine an example of an input array: nums = [1, 3, 2, 3, 1]. The reverse pairs in this example would be (3,1) and (3,1) with indices (1, 4) and (3, 4), respectively. The output for this input should therefore be 2. Given the requirement to find pairs where the condition nums[i] > 2 * nums[j] holds, a brute-force solution would involve checking every possible pair, resulting in an O(n^2) complexity, which is not feasible for larger arrays.

Key DSA Concepts and Theory

Divide and Conquer

One efficient way to solve this problem is through a divide-and-conquer strategy—a fundamental concept also involving algorithms like merge sort. The idea is to divide the array into smaller subarrays, solve the problem for each subarray recursively, and then combine the results. The merge sort algorithm efficiently sorts an array by divide and conquer which could be adapted for counting reverse pairs.

Merge Sort

Merge sort is a classic example of a divide-and-conquer algorithm, typically running in O(n log n) time complexity. It divides the input array into two halves, calls itself for the two halves, and then merges the two sorted halves. We leverage this by modifying the merge function to count reverse pairs in the divided arrays.

Solution Approach

Step-by-Step

  1. Recursive Division (like Merge Sort):

    • Divide the array into halves until reaching subarrays of single elements. This is done recursively.
  2. Count During Merging:

    • While merging two halves, count the valid reverse pairs: for each element i in the left half, find the first element j in the right half, such that nums[i] > 2 * nums[j]. Since each half is sorted, all elements starting from j will maintain this condition.
  3. Merge the Sorted Halves:

    • Merge the two halves back into a single sorted array and return.

Below are implementations of this strategy in C++ and Java:

C++ Code

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        return mergeSort(nums, 0, nums.size() - 1);
    }

    int mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right) return 0;
        int mid = left + (right - left) / 2;
        int count = mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right);
        
        int j = mid + 1;
        for (int i = left; i <= mid; ++i) {
            while (j <= right && nums[i] > 2LL * nums[j]) j++;
            count += j - (mid + 1);
        }

        // Merge the two halves
        vector<int> temp;
        int i = left, k = mid + 1;
        while (i <= mid && k <= right) {
            if (nums[i] <= nums[k]) temp.push_back(nums[i++]);
            else temp.push_back(nums[k++]);
        }
        while (i <= mid) temp.push_back(nums[i++]);
        while (k <= right) temp.push_back(nums[k++]);
        
        for (int i = left; i <= right; ++i) nums[i] = temp[i - left];
        return count;
    }
};

Java Code

class Solution {
    public int reversePairs(int[] nums) {
        return mergeSort(nums, 0, nums.length - 1);
    }

    private int mergeSort(int[] nums, int left, int right) {
        if (left >= right) return 0;
        int mid = left + (right - left) / 2;
        int count = mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right);
        
        int j = mid + 1;
        for (int i = left; i <= mid; i++) {
            while (j <= right && nums[i] > 2.0 * nums[j]) j++;
            count += j - (mid + 1);
        }

        // Merge the two halves
        List<Integer> temp = new ArrayList<>();
        int i = left, k = mid + 1;
        while (i <= mid && k <= right) {
            if (nums[i] <= nums[k]) temp.add(nums[i++]);
            else temp.add(nums[k++]);
        }
        while (i <= mid) temp.add(nums[i++]);
        while (k <= right) temp.add(nums[k++]);
        
        for (i = left; i <= right; i++) nums[i] = temp.get(i - left);
        return count;
    }
}

Time and Space Complexity Analysis

  • Time Complexity: The algorithm typically runs in O(n log n) time due to the divide-and-conquer approach combined with linear time merge operation across the levels of recursion.

  • Space Complexity: The space complexity is O(n) because of the extra space needed for the auxiliary array during merging.

Common Mistakes to Avoid

  • Overflow with Multiplication: Use cautious integer multiplication and consider using long integers to avoid overflow issues, especially with comparisons like nums[i] > 2 * nums[j].
  • Correct Merge Logic: Ensure that during merging, the correct elements are replaced and the array is sorted. Mismatches here could lead to incorrect counting.

Similar Problems on LeetCode

For further practice on similar concepts, consider the following problems:

  • LeetCode 493: Reverse Pairs
  • LeetCode 315: Count of Smaller Numbers After Self

Additional Resources and References

For more detailed insights on divide-and-conquer methods and implementing merge sort proficiently:

  • "Introduction to Algorithms" by Cormen, Leiserson, Rivest, and Stein
  • Online tutorials and coding practice platforms for merge sort and other divide-and-conquer algorithms.

This article ensures a deep dive into solving "Reverse Pairs" using efficient algorithms, providing both understanding and practical coding solutions in C++ and Java.