Reverse Pairs (LeetCode 493)
You will see how merge sort counts the pairs where an earlier value is more than twice a later one, in O(N log N).
You get an integer array nums. A reverse pair is two positions i and j with i < j where the value at i is more than twice the value at j: nums[i] > 2 * nums[j].
Return how many reverse pairs nums holds.
Worked Examples
nums = [1,3,2,3,1]2nums = [2,4,3,5,1]3⚖️Formal Constraints & Bounds
1 <= nums.length <= 5 * 104-231 <= nums[i] <= 231 - 1
Why It Works & Core Invariant
Every pair i < j crosses the middle of exactly one merge. At that moment both halves are sorted, so for each left value the right values it pairs with form a prefix of the right half, and that prefix only grows: one forward pointer counts all cross pairs in linear time.
Real-World Scenario & Production Applications
Comparing two rankings, spotting how far a feed is out of order, or flagging readings that dropped to less than half of an earlier one all count pairs "earlier and bigger than later". Checking every pair is O(N^2); counting them inside a merge sort takes O(N log N), because the sort does the ordering work the count needs.
Step-by-Step Execution Trace Table
The debugger's first preset, the trap case nums = [3,5,2,1] (answer 3):
| Call | Halves after sorting | Cross pairs counted before merging | count | Merged |
|---|---|---|---|---|
sort_count(0, 2) | [3], [5] | 3 > 2 * 5? no: j stays | 0 | [3, 5] |
sort_count(2, 4) | [2], [1] | 2 > 2 * 1? no | 0 | [1, 2] |
sort_count(0, 4) | [3, 5], [1, 2] | i at 3: 3 > 2 * 1 yes, 3 > 2 * 2 no: j - mid = 1. i at 5: j stays past the 1; 5 > 2 * 2 yes: j - mid = 2 | 3 | [1, 2, 3, 5] |
Counting inside the merge loop instead would visit the 2 while 3 is the waiting left value, see 3 > 4 fail, and never count 5 > 4: it returns 2.
| 1 | Every pair `i < j` lies inside the left half, inside the right half, or crosses the middle; the recursive calls handle the first two kinds, so a call only counts pairs that cross its middle. |
| 2 | Keep this true: when `sort_count(lo, hi)` returns, `nums[lo:hi]` is sorted and every pair inside it is counted. So when both calls return, both halves are sorted. |
| 3 | The shape: a recursive function on `[lo, hi)`: return for one value; recurse on both halves; one pass over the left half with a second index into the right half that is never reset; then an ordinary merge written back into `nums[lo:hi]`. |
| 4 | The trap: count in that separate pass, never inside the merge loop. The merge moves by `nums[a] <= nums[b]`, not by `nums[i] > 2 * nums[j]`, so on `[3,5,2,1]` counting while merging returns 2 instead of 3. |
Target: Reverse Pairs (LeetCode 493). The base case of the split.
Intervals sorted by start coordinate; monotonic stack preserves strictly increasing or decreasing element values.
Sweep: if curr.start <= prev.end: merge; Stack: while stack and nums[i] >= stack[-1]: stack.pop().
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Checking every pair is O(N^2). Merge Sort Counting splits the work the way merge sort does. A pair i < j either sits inside the left half, inside the right half, or crosses the middle, with i on the left and j on the right. The first two kinds are counted by the recursive calls. The third kind is counted in sort_count itself, and here the sort pays off: both halves come back sorted, and a left value only needs the right values smaller than half of it. Those are a prefix of the sorted right half, and as nums[i] grows through the sorted left half, that prefix can only grow, so one pointer j moving forward counts every cross pair. Only then are the halves merged, so the parent sees a sorted range too.
🏃 The Analogy: Two Sorted Teams Comparing Scores
Two teams line up by score, lowest first. For each runner on the left team you want to know how many right-team runners scored less than half of them. You don't compare every pair: a flag walks along the right team, and for each left runner, taken in order, you push it forward past every right runner below half their score. A stronger left runner never needs the flag to go back. Then the teams merge into one sorted line, ready for the next round.
🪄 The Mathematical Harmony / Magic Trick
j = midfor i in range(lo, mid): while j < hi and nums[i] > 2 * nums[j]: j += 1 count += j - mid Both halves are sorted when this loop runs, so nums[mid:j] is exactly the right values that pair with nums[i], and a bigger nums[i] only pushes j further: the whole count is O(N) per merge. The trap is counting inside the merge loop, the way inversions are counted: the merge moves by nums[a] <= nums[b], not by the doubled test, so a right value can be placed before every left value that pairs with it has been seen. On [3,5,2,1] that misses 5 > 2 * 2. Count in its own pass first, then merge.
💡 Summary
Recurse on both halves, count cross pairs with a forward-only j while both halves are sorted, then merge. log N levels of O(N) work each: time, space for the merge buffer.
Counting inside the merge loop (the trap): the merge moves by
nums[a] <= nums[b], not bynums[i] > 2 * nums[j], so the inversion-count stepcount += mid - amisses pairs. On[3,5,2,1]it returns 2 instead of 3; count in its own pass, then merge.>=instead of>: equality is not a pair:[2, 1]has none.Halving instead of doubling:
nums[i] // 2 > nums[j]rounds odd and negative values the wrong way ([7, 3, -4, 3]). In Java or C++,2 * nums[j]needs 64 bits: values reach231 - 1.Restarting the pointer:
j = midinside theforloop keeps the answer right but makes each merge quadratic.Not writing the merge back: the parent's count needs
nums[lo:hi]sorted; forgettingnums[lo:hi] = mergedbreaks every level above.
4-Phase Thought Process Model
You will see how a senior engineer spots a pair count over i < j and defends counting during merge sort out loud.
Pattern Recognition Signals
The 10-second spot
Count the positions "i < j" with a comparison between the two values, "nums[i] > 2 * nums[j]", on up to 5 * 104 values: every pair would be over a billion checks. A pair condition over positions that only needs the values sorted on each side is the signal for Merge Sort Counting.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
When sort_count(lo, hi) returns, nums[lo:hi] is sorted and all its pairs are counted. Between the recursive calls and the merge, both halves are sorted, so while j < hi and nums[i] > 2 * nums[j]: j += 1 and count += j - mid count the cross pairs of each nums[i], with j never moving back.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Count in its own pass, before the merge: counting inside the merge loop, as for inversions, misses pairs because the merge moves by
nums[a] <= nums[b], not by the doubled test. On[3,5,2,1]it returns 2 instead of 3.nums[i] > 2 * nums[j], strictly:>=counts[2, 1]as a pair.Double, don't halve:
nums[i] // 2 > nums[j]rounds wrong for odd and negative values, as in[7, 3, -4, 3]. In Java or C++, compute2 * nums[j]in 64 bits.Keep
joutside theforloop: restarting it atmidfor everyiis still correct but makes the count quadratic.
The 60-Second Interview Pitch
Say this out loud before you type a single line
I'd use Merge Sort Counting. Every pair with i before j either sits inside one half of the array or crosses the middle, so I sort and count each half recursively, and count only the crossing pairs myself. When the two halves come back, both are sorted. For a left value, the right values it pairs with are the ones below half of it, which is a prefix of the sorted right half, and that prefix only grows as the left values grow. So one pointer moves forward through the right half and I add its distance from the middle for each left value. Then I merge the halves so my parent gets a sorted range. The trap is counting inside the merge loop like inversions: the merge moves by plain order, not by the doubled condition, so it misses pairs. Each level is linear and there are log N levels:
O(N log N)time,O(N)space.
So: split, count cross pairs with a forward-only pointer while both halves are sorted, then merge.
Complexity & Mathematical Proof
O(N log N)
Look at one call sort_count(lo, hi) with n = hi - lo values. The counting loop runs i over the left half and moves j forward only, so it does at most n steps of i and n / 2 steps of j: O(n). The merge takes each value once: O(n). So T(n) = 2 T(n / 2) + O(n). The recursion has log N levels, and the calls of one level cover all N values once, so each level is O(N). Total: O(N log N).
O(N)
merged holds at most N values at the top call, and each call's buffer is freed before its parent builds its own, so the extra space is O(N). The recursion stack is log N calls deep, O(log N). The answer is one integer.
T(N) = 2 T(N / 2) + O(N) = O(N log N)
Look at one call sort_count(lo, hi) with n = hi - lo values. The counting loop runs i over the left half and moves j forward only, so it does at most n steps of i and n / 2 steps of j: O(n). The merge takes each value once: O(n). So T(n) = 2 T(n / 2) + O(n). The recursion has log N levels, and the calls of one level cover all N values once, so each level is O(N). Total: O(N log N).
Derivation Progression
2 T(n / 2)
sort_count(lo, mid) and sort_count(mid, hi) on halves of size about n / 2.
O(n)
i walks the left half once and j only moves forward through the right half.
O(n)
Each value is appended to merged once and written back once.
O(N log N)
log N levels, each covering N values with O(1) work per value.
Variable Definitions
Number of values, len(nums) (at most 5 * 104)
Size of the range of one call, hi - lo
Memory Architecture & Bounds
O(log N): the recursion depth
O(N): merged, at most N values at the top call
O(1): one integer
Boundary Best / Worst Cases
: every level merges all values
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Triggers: **"i < j"**, **"nums[i] > 2 * nums[j]"**. Count pairs ordered by position that satisfy a comparison between their values, with n up to : Merge Sort Counting, counting the cross pairs of each merge.
: all pairs are about checks. Merge sort counting is , about steps; a Fenwick tree over compressed values is also .
Count in its own pass, never inside the merge loop. 2 * nums[j] overflows 32 bits in Java and C++ (use 64-bit or long); dividing instead, nums[i] / 2 > nums[j], rounds wrong for odd and negative values. The function sorts nums in place: copy it if the caller needs the original order. For data too big for memory, the same count runs as an external merge sort, one sorted run at a time.
Core Algorithmic State Invariants
A pair `i < j` lies inside one half, or crosses the middle of the call where `i` is on the left and `j` on the right: `count = sort_count(lo, mid) + sort_count(mid, hi)`, plus the cross pairs, counts each pair once.
With both halves sorted, `while j < hi and nums[i] > 2 * nums[j]: j += 1` finds the prefix `nums[mid:j]` that pairs with `nums[i]`, and `j` never moves back. The merge moves by `nums[a] <= nums[b]`, so it can't count pairs defined by a different test.
Counting and merging are O(hi - lo) per call and each level of the recursion covers N values once, with `log N` levels: O(N log N) time, O(N) for `merged`, O(log N) recursion stack.
Reverse Pairs (LeetCode 493)
You will see how merge sort counts the pairs where an earlier value is more than twice a later one, in O(N log N).
You get an integer array nums. A reverse pair is two positions i and j with i < j where the value at i is more than twice the value at j: nums[i] > 2 * nums[j].
Return how many reverse pairs nums holds.
Worked Examples
nums = [1,3,2,3,1]2nums = [2,4,3,5,1]3⚖️Formal Constraints & Bounds
1 <= nums.length <= 5 * 104-231 <= nums[i] <= 231 - 1
Why It Works & Core Invariant
Every pair i < j crosses the middle of exactly one merge. At that moment both halves are sorted, so for each left value the right values it pairs with form a prefix of the right half, and that prefix only grows: one forward pointer counts all cross pairs in linear time.
Real-World Scenario & Production Applications
Comparing two rankings, spotting how far a feed is out of order, or flagging readings that dropped to less than half of an earlier one all count pairs "earlier and bigger than later". Checking every pair is O(N^2); counting them inside a merge sort takes O(N log N), because the sort does the ordering work the count needs.
Step-by-Step Execution Trace Table
The debugger's first preset, the trap case nums = [3,5,2,1] (answer 3):
| Call | Halves after sorting | Cross pairs counted before merging | count | Merged |
|---|---|---|---|---|
sort_count(0, 2) | [3], [5] | 3 > 2 * 5? no: j stays | 0 | [3, 5] |
sort_count(2, 4) | [2], [1] | 2 > 2 * 1? no | 0 | [1, 2] |
sort_count(0, 4) | [3, 5], [1, 2] | i at 3: 3 > 2 * 1 yes, 3 > 2 * 2 no: j - mid = 1. i at 5: j stays past the 1; 5 > 2 * 2 yes: j - mid = 2 | 3 | [1, 2, 3, 5] |
Counting inside the merge loop instead would visit the 2 while 3 is the waiting left value, see 3 > 4 fail, and never count 5 > 4: it returns 2.
| 1 | Every pair `i < j` lies inside the left half, inside the right half, or crosses the middle; the recursive calls handle the first two kinds, so a call only counts pairs that cross its middle. |
| 2 | Keep this true: when `sort_count(lo, hi)` returns, `nums[lo:hi]` is sorted and every pair inside it is counted. So when both calls return, both halves are sorted. |
| 3 | The shape: a recursive function on `[lo, hi)`: return for one value; recurse on both halves; one pass over the left half with a second index into the right half that is never reset; then an ordinary merge written back into `nums[lo:hi]`. |
| 4 | The trap: count in that separate pass, never inside the merge loop. The merge moves by `nums[a] <= nums[b]`, not by `nums[i] > 2 * nums[j]`, so on `[3,5,2,1]` counting while merging returns 2 instead of 3. |
Target: Reverse Pairs (LeetCode 493). The base case of the split.
Intervals sorted by start coordinate; monotonic stack preserves strictly increasing or decreasing element values.
Sweep: if curr.start <= prev.end: merge; Stack: while stack and nums[i] >= stack[-1]: stack.pop().
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Checking every pair is O(N^2). Merge Sort Counting splits the work the way merge sort does. A pair i < j either sits inside the left half, inside the right half, or crosses the middle, with i on the left and j on the right. The first two kinds are counted by the recursive calls. The third kind is counted in sort_count itself, and here the sort pays off: both halves come back sorted, and a left value only needs the right values smaller than half of it. Those are a prefix of the sorted right half, and as nums[i] grows through the sorted left half, that prefix can only grow, so one pointer j moving forward counts every cross pair. Only then are the halves merged, so the parent sees a sorted range too.
🏃 The Analogy: Two Sorted Teams Comparing Scores
Two teams line up by score, lowest first. For each runner on the left team you want to know how many right-team runners scored less than half of them. You don't compare every pair: a flag walks along the right team, and for each left runner, taken in order, you push it forward past every right runner below half their score. A stronger left runner never needs the flag to go back. Then the teams merge into one sorted line, ready for the next round.
🪄 The Mathematical Harmony / Magic Trick
j = midfor i in range(lo, mid): while j < hi and nums[i] > 2 * nums[j]: j += 1 count += j - mid Both halves are sorted when this loop runs, so nums[mid:j] is exactly the right values that pair with nums[i], and a bigger nums[i] only pushes j further: the whole count is O(N) per merge. The trap is counting inside the merge loop, the way inversions are counted: the merge moves by nums[a] <= nums[b], not by the doubled test, so a right value can be placed before every left value that pairs with it has been seen. On [3,5,2,1] that misses 5 > 2 * 2. Count in its own pass first, then merge.
💡 Summary
Recurse on both halves, count cross pairs with a forward-only j while both halves are sorted, then merge. log N levels of O(N) work each: time, space for the merge buffer.
Counting inside the merge loop (the trap): the merge moves by
nums[a] <= nums[b], not bynums[i] > 2 * nums[j], so the inversion-count stepcount += mid - amisses pairs. On[3,5,2,1]it returns 2 instead of 3; count in its own pass, then merge.>=instead of>: equality is not a pair:[2, 1]has none.Halving instead of doubling:
nums[i] // 2 > nums[j]rounds odd and negative values the wrong way ([7, 3, -4, 3]). In Java or C++,2 * nums[j]needs 64 bits: values reach231 - 1.Restarting the pointer:
j = midinside theforloop keeps the answer right but makes each merge quadratic.Not writing the merge back: the parent's count needs
nums[lo:hi]sorted; forgettingnums[lo:hi] = mergedbreaks every level above.
4-Phase Thought Process Model
You will see how a senior engineer spots a pair count over i < j and defends counting during merge sort out loud.
Pattern Recognition Signals
The 10-second spot
Count the positions "i < j" with a comparison between the two values, "nums[i] > 2 * nums[j]", on up to 5 * 104 values: every pair would be over a billion checks. A pair condition over positions that only needs the values sorted on each side is the signal for Merge Sort Counting.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
When sort_count(lo, hi) returns, nums[lo:hi] is sorted and all its pairs are counted. Between the recursive calls and the merge, both halves are sorted, so while j < hi and nums[i] > 2 * nums[j]: j += 1 and count += j - mid count the cross pairs of each nums[i], with j never moving back.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Count in its own pass, before the merge: counting inside the merge loop, as for inversions, misses pairs because the merge moves by
nums[a] <= nums[b], not by the doubled test. On[3,5,2,1]it returns 2 instead of 3.nums[i] > 2 * nums[j], strictly:>=counts[2, 1]as a pair.Double, don't halve:
nums[i] // 2 > nums[j]rounds wrong for odd and negative values, as in[7, 3, -4, 3]. In Java or C++, compute2 * nums[j]in 64 bits.Keep
joutside theforloop: restarting it atmidfor everyiis still correct but makes the count quadratic.
The 60-Second Interview Pitch
Say this out loud before you type a single line
I'd use Merge Sort Counting. Every pair with i before j either sits inside one half of the array or crosses the middle, so I sort and count each half recursively, and count only the crossing pairs myself. When the two halves come back, both are sorted. For a left value, the right values it pairs with are the ones below half of it, which is a prefix of the sorted right half, and that prefix only grows as the left values grow. So one pointer moves forward through the right half and I add its distance from the middle for each left value. Then I merge the halves so my parent gets a sorted range. The trap is counting inside the merge loop like inversions: the merge moves by plain order, not by the doubled condition, so it misses pairs. Each level is linear and there are log N levels:
O(N log N)time,O(N)space.
So: split, count cross pairs with a forward-only pointer while both halves are sorted, then merge.
Complexity & Mathematical Proof
O(N log N)
Look at one call sort_count(lo, hi) with n = hi - lo values. The counting loop runs i over the left half and moves j forward only, so it does at most n steps of i and n / 2 steps of j: O(n). The merge takes each value once: O(n). So T(n) = 2 T(n / 2) + O(n). The recursion has log N levels, and the calls of one level cover all N values once, so each level is O(N). Total: O(N log N).
O(N)
merged holds at most N values at the top call, and each call's buffer is freed before its parent builds its own, so the extra space is O(N). The recursion stack is log N calls deep, O(log N). The answer is one integer.
T(N) = 2 T(N / 2) + O(N) = O(N log N)
Look at one call sort_count(lo, hi) with n = hi - lo values. The counting loop runs i over the left half and moves j forward only, so it does at most n steps of i and n / 2 steps of j: O(n). The merge takes each value once: O(n). So T(n) = 2 T(n / 2) + O(n). The recursion has log N levels, and the calls of one level cover all N values once, so each level is O(N). Total: O(N log N).
Derivation Progression
2 T(n / 2)
sort_count(lo, mid) and sort_count(mid, hi) on halves of size about n / 2.
O(n)
i walks the left half once and j only moves forward through the right half.
O(n)
Each value is appended to merged once and written back once.
O(N log N)
log N levels, each covering N values with O(1) work per value.
Variable Definitions
Number of values, len(nums) (at most 5 * 104)
Size of the range of one call, hi - lo
Memory Architecture & Bounds
O(log N): the recursion depth
O(N): merged, at most N values at the top call
O(1): one integer
Boundary Best / Worst Cases
: every level merges all values
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Triggers: **"i < j"**, **"nums[i] > 2 * nums[j]"**. Count pairs ordered by position that satisfy a comparison between their values, with n up to : Merge Sort Counting, counting the cross pairs of each merge.
: all pairs are about checks. Merge sort counting is , about steps; a Fenwick tree over compressed values is also .
Count in its own pass, never inside the merge loop. 2 * nums[j] overflows 32 bits in Java and C++ (use 64-bit or long); dividing instead, nums[i] / 2 > nums[j], rounds wrong for odd and negative values. The function sorts nums in place: copy it if the caller needs the original order. For data too big for memory, the same count runs as an external merge sort, one sorted run at a time.
Core Algorithmic State Invariants
A pair `i < j` lies inside one half, or crosses the middle of the call where `i` is on the left and `j` on the right: `count = sort_count(lo, mid) + sort_count(mid, hi)`, plus the cross pairs, counts each pair once.
With both halves sorted, `while j < hi and nums[i] > 2 * nums[j]: j += 1` finds the prefix `nums[mid:j]` that pairs with `nums[i]`, and `j` never moves back. The merge moves by `nums[a] <= nums[b]`, so it can't count pairs defined by a different test.
Counting and merging are O(hi - lo) per call and each level of the recursion covers N values once, with `log N` levels: O(N log N) time, O(N) for `merged`, O(log N) recursion stack.
| Canonical Invariant | Concrete Code | Engineering Rationale |
|---|---|---|
| One value holds no pair | if hi - lo <= 1:
return 0 | The base case of the split. |
| Count inside each half; each comes back sorted | count = sort_count(lo, mid) + sort_count(mid, hi) | Pairs that don't cross the middle belong to exactly one half. |
| A forward-only pointer into the sorted right half | while j < hi and nums[i] > 2 * nums[j]:
j += 1 | The pair condition picks a prefix of the right half, and a bigger left value only lengthens it. |
| Count the cross pairs of this left value | count += j - mid | `nums[mid:j]` all pair with `nums[i]`. |
| Merge only after counting | if nums[a] <= nums[b]:
merged.append(nums[a])
a += 1 | The trap: the merge moves by plain order, not by the pair condition, so it can't do the counting. |
| Hand a sorted range to the parent | nums[lo:hi] = merged | The parent's count relies on this range being sorted. |