Description
Reverse Pairs
Given an integer array nums, return the number of reverse pairs in the array.
A reverse pair is a pair (i, j) where:
0 <= i < j < nums.lengthandnums[i] > 2 * nums[j].
Example 1:
Input: nums = [1,3,2,3,1]
Output: 2
Explanation: The reverse pairs are:
(1, 4) --> nums[1] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) --> nums[3] = 3, nums[4] = 1, 3 > 2 * 1
Example 2:
Input: nums = [2,4,3,5,1]
Output: 3
Explanation: The reverse pairs are:
(1, 4) --> nums[1] = 4, nums[4] = 1, 4 > 2 * 1
(2, 4) --> nums[2] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) --> nums[3] = 5, nums[4] = 1, 5 > 2 * 1
Constraints:
1 <= nums.length <= 5 * 104-231 <= nums[i] <= 231 - 1
Brute Force
Explanation
We check every possible pair where using two nested loops and increment our counter whenever holds true.
Complexity Analysis
- Time Complexity: — Two nested loops over elements.
- Space Complexity: — Uses no extra memory.
class Solution {
public int reversePairs(int[] nums) {
int count = 0;
int n = nums.length;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// Multiplying by 2.0 promotes comparison to double to avoid integer overflow
if (nums[i] > 2.0 * nums[j]) {
count++;
}
}
}
return count;
}
}
Optimized (Modified Merge Sort)
Merge Sort Overview
Merge Sort
Standard Merge Sort splits an array into two sorted halves recursively and zips them back together in linear time ().
What We Added: countPairs()
Right before merging two sorted halves, we insert a two-pointer scan step: countPairs().
Since both halves are individually sorted:
- As
leftmoves forward to larger elements in the left half, valid elements in the right half only ever grow. - The
rightpointer never resets back to the start of the right half. It moves continuously forward, allowing us to count all valid cross-pairs in linear time per recursion step instead of .
Complexity Analysis
- Time Complexity: — Recursion tree depth is , with work (counting + merging) at each level.
- Space Complexity: — For the auxiliary
temparray during merging.
class Solution {
public int reversePairs(int[] nums) {
return mergeSort(nums, 0, nums.length - 1);
}
public int mergeSort(int[] arr, int low, int high) {
if (low >= high) return 0;
int mid = low + (high - low) / 2;
// 1. DIVIDE: Sum counts from left and right halves
int count = mergeSort(arr, low, mid) + mergeSort(arr, mid + 1, high);
// 2. COUNT: Count cross reverse pairs before merging
count += countPairs(arr, low, mid, high);
// 3. COMBINE: Standard merge step
merge(arr, low, mid, high);
return count;
}
// Two-pointer scan on sorted halves
private int countPairs(int[] arr, int low, int mid, int high) {
int count = 0;
int right = mid + 1;
for (int left = low; left <= mid; left++) {
while (right <= high && (long) arr[left] > 2L * arr[right]) {
right++; //long imp for full pass
}
count += (right - (mid + 1));
}
return count;
}
// Standard Merge helper
private void merge(int[] arr, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int left = low, right = mid + 1, k = 0;
while (left <= mid && right <= high) {
temp[k++] = (arr[left] <= arr[right]) ? arr[left++] : arr[right++];
}
while (left <= mid) temp[k++] = arr[left++];
while (right <= high) temp[k++] = arr[right++];
for (int i = low; i <= high; i++) {
arr[i] = temp[i - low];
}
}
}
Key Takeaway: Integer Overflow in Comparison Operations
- The Trap: In Java, binary operations (like multiplication
*) execute using the primitive types of the operands before any comparative statement is evaluated. - The Case: For constraints reach (
Integer.MAX_VALUE). Evaluating2 * nums[right]with standard 32-bit integer arithmetic wraps around to negative values when . - Why
(long) nums[left] > 2 * nums[right]Fails: Casting only the left side converts tolong, but the right side2 * nums[right]still evaluates as standard 32-bitintmultiplication first (overflowing to negative values) before being promoted tolongfor comparison. - The Solution: Cast either the constant multiplier to a long literal (
2L) or cast tolongto force 64-bit promotion during multiplication:
while (right <= high && (long) nums[left] > 2L * nums[right]) right++;The key reason we write it this way comes down to speed and how sorted arrays behave.
If you write a straightforward condition, you would check every right from scratch for every single left:
// Naive straight-forward counting: O(n^2)
for (int left = low; left <= mid; left++) {
for (int right = mid + 1; right <= high; right++) {
if (arr[left] > 2.0 * arr[right]) {
count++;
}
}
}
This brute-force double loop checks all combinations, making it slow () and defeating the whole purpose of Merge Sort.
The Smart Way: Why right Keeps Moving Forward
Since both halves are already sorted in ascending order:
rightonly pushes forward: Ifarr[left] > 2.0 * arr[right]is true forarr[right], thewhileloop movesrightforward until it finds a number that is too big to satisfy the condition.rightpoints to the boundary: When thewhileloop stops, every element in the right half from indexmid + 1up toright - 1is valid!- How many valid elements is that?
Walkthrough Example
Suppose our sorted halves are:
- Left half:
[6, 10](low = 0,mid = 1) - Right half:
[1, 2, 4](mid + 1 = 2,high = 4)
Step 1: left = 0 (arr[left] = 6)
- Start
right = 2(arr[right] = 1). 6 > 2.0 * 1Valid! Moverightto3(arr[right] = 2).6 > 2.0 * 2Valid! Moverightto4(arr[right] = 4).6 > 2.0 * 4False (6is not> 8). Loop stops atright = 4.- Count calculation:
right - (mid + 1) = 4 - 2 = 2.
(Elements1and2were valid).
Step 2: left = 1 (arr[left] = 10)
- Because
10is larger than6, we do not resetrightback to 2! Elements1and2are guaranteed to satisfy10 > 2 * arr[right]. - We resume checking from
right = 4(arr[right] = 4): 10 > 2.0 * 4Valid! Moverightto5(high + 1). Loop stops.- Count calculation:
right - (mid + 1) = 5 - 2 = 3.
(Elements1,2, and4are valid).
Because right never resets back to mid + 1, the inner while loop runs at most times across the entire outer loop, giving us an optimal time complexity.