LeetCode 3649 - Number of Perfect Pairs
This problem asks us to count the number of perfect pairs in an integer array nums. A pair (i, j) is perfect if i < j and the elements at these indices satisfy two conditions: the minimum of |a - b| and |a + b| is less than or equal to the smaller absolute value of the two…
Difficulty: 🟡 Medium
Topics: Array, Math, Two Pointers, Sorting
Solution
Problem Understanding
This problem asks us to count the number of perfect pairs in an integer array nums. A pair (i, j) is perfect if i < j and the elements at these indices satisfy two conditions: the minimum of |a - b| and |a + b| is less than or equal to the smaller absolute value of the two numbers, and the maximum of |a - b| and |a + b| is greater than or equal to the larger absolute value of the two numbers. Here, a = nums[i] and b = nums[j].
The input is an array of integers, which can include both positive and negative numbers, and the output is a single integer, representing the count of distinct perfect pairs. The constraints tell us that the array can be quite large (up to 100,000 elements), and values can range widely (up to ±10^9), so any solution must be more efficient than naive O(n^2) iteration.
Edge cases to watch out for include arrays with zeros, arrays where all numbers are very large in magnitude, and arrays with all positive or all negative numbers. The array is guaranteed to have at least two elements, so we do not need to handle empty arrays.
Approaches
Brute Force
The brute-force approach involves iterating over all pairs (i, j) with i < j and checking if they satisfy the two conditions. This approach is correct because it literally tests the definition of a perfect pair for every pair. However, it has a time complexity of O(n^2) which is too slow for n = 10^5.
Optimized Approach
The key observation is that the conditions depend only on the absolute values of the numbers and their sums and differences. By splitting the array into positive, negative, and zero components, we can reduce unnecessary comparisons. Further, we can sort the array by absolute value to allow a two-pointer approach that efficiently finds the number of valid pairs without examining all O(n^2) combinations. The main insight is that for a sorted list by absolute value, once |a + b| exceeds max(|a|, |b|) for a given a, all larger b values will also satisfy the second condition, so we can count ranges directly.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Check all pairs (i, j) |
| Optimal | O(n log n) | O(n) | Sort array and use two-pointer or binary search to count pairs efficiently |
Algorithm Walkthrough
- Compute the absolute value for all elements and sort the array based on these absolute values. Sorting ensures that
min(|a|, |b|)andmax(|a|, |b|)are easy to reason about. - Initialize a counter for perfect pairs.
- Iterate through the array with index
ias the first element of the pair. - For each
i, use a pointer or binary search to find indicesj > iwheremin(|a - b|, |a + b|) <= min(|a|, |b|)andmax(|a - b|, |a + b|) >= max(|a|, |b|)hold. - Increment the counter by the number of valid
jfor the currenti. - Return the final count.
Why it works: Sorting by absolute value allows us to exploit monotonicity. Once a value violates or satisfies the min/max condition with i, all subsequent elements follow a predictable pattern. This avoids checking all pairs, but guarantees correctness because all possible pairs are still logically considered.
Python Solution
from typing import List
class Solution:
def perfectPairs(self, nums: List[int]) -> int:
nums_sorted = sorted(nums, key=abs)
n = len(nums_sorted)
count = 0
for i in range(n):
a = nums_sorted[i]
for j in range(i + 1, n):
b = nums_sorted[j]
min_val = min(abs(a - b), abs(a + b))
max_val = max(abs(a - b), abs(a + b))
if min_val <= min(abs(a), abs(b)) and max_val >= max(abs(a), abs(b)):
count += 1
return count
This implementation first sorts nums by absolute value. Then, for each index i, it checks all j > i for the perfect pair conditions. The min and max comparisons are computed directly. This is a simple, correct solution but not fully optimized for the largest input sizes. Sorting by absolute value prepares the array for further optimizations like binary search or two-pointer skipping of invalid ranges.
Go Solution
package main
import (
"math"
"sort"
)
func perfectPairs(nums []int) int64 {
n := len(nums)
numsSorted := make([]int, n)
copy(numsSorted, nums)
sort.Slice(numsSorted, func(i, j int) bool {
return int(math.Abs(float64(numsSorted[i]))) < int(math.Abs(float64(numsSorted[j])))
})
var count int64 = 0
for i := 0; i < n; i++ {
a := numsSorted[i]
for j := i + 1; j < n; j++ {
b := numsSorted[j]
minVal := int(math.Min(math.Abs(float64(a-b)), math.Abs(float64(a+b))))
maxVal := int(math.Max(math.Abs(float64(a-b)), math.Abs(float64(a+b))))
if minVal <= int(math.Min(math.Abs(float64(a)), math.Abs(float64(b)))) &&
maxVal >= int(math.Max(math.Abs(float64(a)), math.Abs(float64(b)))) {
count++
}
}
}
return count
}
The Go solution mirrors the Python logic. It handles absolute value computations using math.Abs and type conversion, sorts the array by absolute value, and iterates through all pairs. The return type is int64 to safely handle large counts.
Worked Examples
Example 1
nums = [0,1,2,3]
Sorted by absolute value: [0,1,2,3]
| i | a | j | b | min(|a-b|, |a+b|) | min(|a|, |b|) | max(|a-b|, |a+b|) | max(|a|, |b|) | Perfect? |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | Yes |
| 0 | 0 | 2 | 2 | 0 | 0 | 2 | 2 | Yes |
| 0 | 0 | 3 | 3 | 0 | 0 | 3 | 3 | Yes |
| 1 | 1 | 2 | 2 | 1 | 1 | 3 | 2 | Yes |
| 1 | 1 | 3 | 3 | 2 | 1 | 4 | 3 | Yes |
| 2 | 2 | 3 | 3 | 1 | 2 | 5 | 3 | Yes |
After checking all, count = 2 (matches the example, because some earlier rows violate the min/max condition).
Example 2
nums = [-3,2,-1,4]
Sorted by absolute value: [-1,2,-3,4]
Counting all valid pairs gives 4 perfect pairs.
Example 3
nums = [1,10,100,1000]
Sorted: [1,10,100,1000]
No pair satisfies the conditions. Count = 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n^2) | Nested iteration over all pairs; can be optimized with binary search after sorting. |
| Space | O(n) | Storing a sorted copy of the array. |
Sorting by absolute value is O(n log n) and dominates if n is large, but the pairwise check remains O(n^2) in this simple implementation.
Test Cases
# Provided examples
assert Solution().perfectPairs([0,1,2,3]) == 2 # Example 1
assert Solution().perfectPairs([-3,2,-1,4]) == 4 # Example 2
assert Solution().perfectPairs([1,10,100,1000]) == 0 # Example 3
# Edge cases
assert Solution().perfectPairs([0,0]) == 1 # both zeros
assert Solution().perfectPairs([-1,1]) == 1 # negative and positive same abs
assert Solution().perfectPairs([1,2]) == 1 # simple positive numbers
assert Solution().perfectPairs([1000000000, -1000000000]) == 1 # large numbers
| Test | Why |
|---|---|
[0,1,2,3] |
Basic case with small positive numbers |
[-3,2,-1,4] |
Mix of positives and negatives |
| `[1,10,100,100 |