719. Find K-th Smallest Pair Distance
Problem Description
You are given an integer array nums
and an integer k
. The problem asks you to find the k
-th smallest distance among all possible pairs of elements in the array.
A distance of a pair is defined as the absolute difference between two elements. For example, if you have elements a
and b
, their distance is |a - b|
.
You need to consider all possible pairs (nums[i], nums[j])
where i < j
. This means you're looking at all combinations of two different elements from the array where the first element comes before the second element in terms of index position.
Among all these pairs and their corresponding distances, you need to return the k
-th smallest distance value.
Example walkthrough:
- If
nums = [1, 3, 1]
andk = 1
- All possible pairs are:
(1,3)
,(1,1)
,(3,1)
- Their distances are:
|1-3| = 2
,|1-1| = 0
,|3-1| = 2
- Sorted distances:
[0, 2, 2]
- The 1st smallest distance is
0
The solution uses binary search on the answer space combined with a counting function. It first sorts the array, then performs binary search on the range of possible distances [0, max(nums) - min(nums)]
. For each candidate distance, it counts how many pairs have a distance less than or equal to that value. The binary search finds the smallest distance where at least k
pairs have a distance less than or equal to it.
Intuition
The key insight is recognizing that this is a "find the k-th smallest value" problem where we're not looking for the k-th smallest element, but rather the k-th smallest distance. Since we need the k-th smallest among potentially millions of distances (up to n*(n-1)/2
pairs), computing and sorting all distances would be inefficient.
Instead of generating all distances explicitly, we can use binary search on the answer. The possible distance values range from 0
(when two elements are equal) to max(nums) - min(nums)
(the maximum possible difference).
The crucial observation is: if we pick any distance value d
, we can efficiently count how many pairs have a distance less than or equal to d
. If this count is less than k
, then our k-th smallest distance must be larger than d
. If the count is at least k
, then our answer is at most d
.
To count pairs efficiently, we first sort the array. For each element nums[i]
, we want to find how many elements before it have a distance of at most d
from nums[i]
. This means finding elements nums[j]
where j < i
and nums[i] - nums[j] <= d
, which translates to nums[j] >= nums[i] - d
. Since the array is sorted, we can use binary search to find the leftmost position where this condition holds, giving us the count in logarithmic time.
This transforms the problem from "find the k-th smallest distance" to "find the smallest distance d
such that at least k
pairs have distance <= d
", which is perfectly suited for binary search on the answer space.
Learn more about Two Pointers, Binary Search and Sorting patterns.
Solution Approach
The implementation uses binary search on the answer space combined with a counting helper function. Here's how the solution works step by step:
1. Sort the array:
nums.sort()
Sorting is crucial as it allows us to efficiently count pairs using binary search later.
2. Define the counting function:
def count(dist):
cnt = 0
for i, b in enumerate(nums):
a = b - dist
j = bisect_left(nums, a, 0, i)
cnt += i - j
return cnt
This function counts how many pairs have a distance less than or equal to dist
. For each element nums[i]
(referred to as b
in the code):
- We want to find pairs
(nums[j], nums[i])
wherej < i
andnums[i] - nums[j] <= dist
- This means
nums[j] >= nums[i] - dist
(which is stored asa
) bisect_left(nums, a, 0, i)
finds the leftmost indexj
in the range[0, i)
wherenums[j] >= a
- The count of valid pairs with
nums[i]
as the larger element isi - j
3. Binary search on the answer:
return bisect_left(range(nums[-1] - nums[0]), k, key=count)
This clever use of bisect_left
performs binary search on the range [0, nums[-1] - nums[0])
:
- The search space is all possible distances from
0
to the maximum possible distance - The
key=count
parameter means we're searching for the leftmost distance value wherecount(dist) >= k
bisect_left
finds the smallest distanced
such that the number of pairs with distance<= d
is at leastk
Time Complexity: O(n log n * log(max-min))
where:
O(n log n)
for initial sorting- Binary search runs
O(log(max-min))
iterations - Each iteration calls
count()
which takesO(n log n)
time (n iterations, each with a binary search)
Space Complexity: O(1)
excluding the sorting space, as we only use a constant amount of extra variables.
The elegance of this solution lies in avoiding the explicit generation of all n*(n-1)/2
distances, instead using binary search to directly find the k-th smallest value.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 6, 1]
and k = 3
.
Step 1: Sort the array
- Original:
[1, 6, 1]
- Sorted:
[1, 1, 6]
Step 2: Determine search range
- Minimum possible distance:
0
- Maximum possible distance:
6 - 1 = 5
- Search range:
[0, 5]
Step 3: Binary search on distances
First, let's see what distances actually exist:
- Pairs:
(1,1)
,(1,6)
,(1,6)
- Distances:
0
,5
,5
- Sorted:
[0, 5, 5]
- We want the 3rd smallest, which is
5
Now let's trace the binary search:
Iteration 1: Try mid = 2
- Count pairs with distance ≤ 2:
- For
nums[0]=1
: No elements before it → 0 pairs - For
nums[1]=1
: Checknums[j] ≥ 1-2 = -1
. All elements before index 1 satisfy this (index 0), so 1 pair - For
nums[2]=6
: Checknums[j] ≥ 6-2 = 4
. No elements before index 2 satisfy this, so 0 pairs - Total: 1 pair
- For
- Since 1 < 3, we need a larger distance. Search in
[3, 5]
Iteration 2: Try mid = 4
- Count pairs with distance ≤ 4:
- For
nums[0]=1
: 0 pairs - For
nums[1]=1
: Checknums[j] ≥ 1-4 = -3
. Element at index 0 satisfies this, so 1 pair - For
nums[2]=6
: Checknums[j] ≥ 6-4 = 2
. No elements before index 2 satisfy this, so 0 pairs - Total: 1 pair
- For
- Since 1 < 3, we need a larger distance. Search in
[5, 5]
Iteration 3: Try mid = 5
- Count pairs with distance ≤ 5:
- For
nums[0]=1
: 0 pairs - For
nums[1]=1
: Checknums[j] ≥ 1-5 = -4
. Element at index 0 satisfies this, so 1 pair - For
nums[2]=6
: Checknums[j] ≥ 6-5 = 1
. Elements at indices 0 and 1 both equal 1, so 2 pairs - Total: 3 pairs
- For
- Since 3 ≥ 3, this distance works!
The binary search finds that 5
is the smallest distance where at least 3 pairs have distance ≤ that value. Therefore, the 3rd smallest distance is 5
.
Solution Implementation
1class Solution:
2 def smallestDistancePair(self, nums: List[int], k: int) -> int:
3 def count_pairs_with_distance_at_most(max_distance):
4 """
5 Count the number of pairs with distance <= max_distance.
6 For each element, find how many elements before it have
7 distance <= max_distance from it.
8 """
9 pair_count = 0
10
11 # For each element at position i
12 for i, current_num in enumerate(nums):
13 # Find the smallest element that satisfies:
14 # current_num - element <= max_distance
15 # which means: element >= current_num - max_distance
16 min_valid_value = current_num - max_distance
17
18 # Binary search for the leftmost position where
19 # nums[j] >= min_valid_value in range [0, i)
20 left_bound_index = bisect_left(nums, min_valid_value, 0, i)
21
22 # All elements from left_bound_index to i-1 form valid pairs with current_num
23 pair_count += i - left_bound_index
24
25 return pair_count
26
27 # Sort the array to enable binary search
28 nums.sort()
29
30 # Binary search on the answer space [0, max_difference]
31 # Find the smallest distance where count_pairs_with_distance_at_most(distance) >= k
32 max_possible_distance = nums[-1] - nums[0]
33 return bisect_left(range(max_possible_distance + 1), k, key=count_pairs_with_distance_at_most)
34
1class Solution {
2 /**
3 * Finds the k-th smallest distance among all pairs of integers in the array.
4 * Uses binary search on the answer space (distance values).
5 *
6 * @param nums the input array of integers
7 * @param k the k-th smallest distance to find (1-indexed)
8 * @return the k-th smallest distance
9 */
10 public int smallestDistancePair(int[] nums, int k) {
11 // Sort the array to enable binary search
12 Arrays.sort(nums);
13
14 // Binary search on the distance value
15 // Minimum possible distance is 0, maximum is the difference between max and min elements
16 int minDistance = 0;
17 int maxDistance = nums[nums.length - 1] - nums[0];
18
19 while (minDistance < maxDistance) {
20 int midDistance = (minDistance + maxDistance) >> 1; // Equivalent to (minDistance + maxDistance) / 2
21
22 // Count how many pairs have distance <= midDistance
23 if (count(midDistance, nums) >= k) {
24 // If count >= k, the k-th smallest distance is at most midDistance
25 maxDistance = midDistance;
26 } else {
27 // If count < k, the k-th smallest distance is greater than midDistance
28 minDistance = midDistance + 1;
29 }
30 }
31
32 return minDistance;
33 }
34
35 /**
36 * Counts the number of pairs in the sorted array with distance <= targetDistance.
37 * For each element, uses binary search to find the leftmost element that forms
38 * a valid pair (distance <= targetDistance).
39 *
40 * @param targetDistance the maximum distance to consider
41 * @param nums the sorted array of integers
42 * @return the count of pairs with distance <= targetDistance
43 */
44 private int count(int targetDistance, int[] nums) {
45 int pairCount = 0;
46
47 // For each element at index i, find all valid pairs where nums[i] is the larger element
48 for (int i = 0; i < nums.length; ++i) {
49 // Binary search to find the leftmost index where nums[i] - nums[index] <= targetDistance
50 int leftIndex = 0;
51 int rightIndex = i;
52
53 while (leftIndex < rightIndex) {
54 int midIndex = (leftIndex + rightIndex) >> 1; // Equivalent to (leftIndex + rightIndex) / 2
55
56 // Calculate the minimum value that can form a valid pair with nums[i]
57 int minimumValue = nums[i] - targetDistance;
58
59 if (nums[midIndex] >= minimumValue) {
60 // nums[midIndex] can form a valid pair, try to find a smaller index
61 rightIndex = midIndex;
62 } else {
63 // nums[midIndex] is too small, search in the right half
64 leftIndex = midIndex + 1;
65 }
66 }
67
68 // All elements from leftIndex to i-1 can form valid pairs with nums[i]
69 pairCount += i - leftIndex;
70 }
71
72 return pairCount;
73 }
74}
75
1class Solution {
2public:
3 int smallestDistancePair(vector<int>& nums, int k) {
4 // Sort the array to enable binary search on distances
5 sort(nums.begin(), nums.end());
6
7 // Binary search on the answer: the range of possible distances
8 // Minimum distance is 0, maximum is the difference between largest and smallest elements
9 int left = 0;
10 int right = nums.back() - nums.front();
11
12 while (left < right) {
13 int mid = left + (right - left) / 2;
14
15 // Count how many pairs have distance <= mid
16 if (countPairsWithDistanceLessThanOrEqual(mid, nums) >= k) {
17 // If we have at least k pairs with distance <= mid,
18 // the answer might be mid or smaller
19 right = mid;
20 } else {
21 // If we have fewer than k pairs with distance <= mid,
22 // we need a larger distance
23 left = mid + 1;
24 }
25 }
26
27 return left;
28 }
29
30private:
31 // Count the number of pairs with distance <= maxDistance
32 int countPairsWithDistanceLessThanOrEqual(int maxDistance, vector<int>& nums) {
33 int pairCount = 0;
34
35 // For each element nums[i], count how many elements before it
36 // have a distance <= maxDistance from nums[i]
37 for (int i = 0; i < nums.size(); ++i) {
38 // Find the smallest element that is >= (nums[i] - maxDistance)
39 // All elements from this position to i-1 will form valid pairs with nums[i]
40 int targetValue = nums[i] - maxDistance;
41 int leftBoundIndex = lower_bound(nums.begin(), nums.end(), targetValue) - nums.begin();
42
43 // Add the number of valid pairs with nums[i] as the larger element
44 pairCount += i - leftBoundIndex;
45 }
46
47 return pairCount;
48 }
49};
50
1/**
2 * Finds the k-th smallest distance among all pairs in the array
3 * @param nums - Input array of integers
4 * @param k - The k-th smallest distance to find (1-indexed)
5 * @returns The k-th smallest distance
6 */
7function smallestDistancePair(nums: number[], k: number): number {
8 // Sort the array in ascending order to enable binary search on distances
9 nums.sort((a, b) => a - b);
10
11 const arrayLength: number = nums.length;
12
13 // Binary search on the answer: the distance range is [0, max - min]
14 let minDistance: number = 0;
15 let maxDistance: number = nums[arrayLength - 1] - nums[0];
16
17 while (minDistance < maxDistance) {
18 // Calculate mid-point distance to test
19 const midDistance: number = (minDistance + maxDistance) >> 1;
20
21 // Count how many pairs have distance <= midDistance
22 let pairCount: number = 0;
23 let leftPointer: number = 0;
24
25 // Use sliding window technique with two pointers
26 for (let rightPointer: number = 0; rightPointer < arrayLength; rightPointer++) {
27 // Move left pointer until the distance from nums[leftPointer] to nums[rightPointer] is <= midDistance
28 while (nums[rightPointer] - nums[leftPointer] > midDistance) {
29 leftPointer++;
30 }
31
32 // All elements from leftPointer to rightPointer-1 form valid pairs with nums[rightPointer]
33 // These pairs have distance <= midDistance
34 pairCount += rightPointer - leftPointer;
35 }
36
37 // If we have at least k pairs with distance <= midDistance,
38 // the answer is at most midDistance
39 if (pairCount >= k) {
40 maxDistance = midDistance;
41 } else {
42 // Otherwise, we need a larger distance
43 minDistance = midDistance + 1;
44 }
45 }
46
47 return minDistance;
48}
49
Time and Space Complexity
Time Complexity: O(n log n + n log(max_diff))
The time complexity consists of two main parts:
- Sorting the array takes
O(n log n)
wheren
is the length ofnums
- The binary search on the range
[0, nums[-1] - nums[0]]
takesO(log(max_diff))
iterations, wheremax_diff = nums[-1] - nums[0]
is the maximum possible distance - For each binary search iteration, the
count
function is called, which:- Iterates through all
n
elements - For each element, performs a
bisect_left
operation which takesO(log n)
- Therefore,
count
function takesO(n log n)
time
- Iterates through all
- Total for binary search part:
O(n log n × log(max_diff))
Since the sorting dominates when max_diff
is small, and the binary search dominates when max_diff
is large, the overall complexity is O(n log n + n log n × log(max_diff))
= O(n log n × log(max_diff))
.
However, note that max_diff
is bounded by the range of values in the array, not by n
. If we consider the maximum value in the array as M
, then the complexity can also be written as O(n log n × log M)
.
Space Complexity: O(1)
or O(n)
- If we consider the sorting to be in-place (like Python's sort which modifies the list), the extra space used is
O(1)
- If we need to account for the sorting algorithm's internal space usage, it would be
O(log n)
for the recursion stack in typical sorting implementations - The
bisect_left
withrange()
in Python 3 creates an iterator (not a list), so it usesO(1)
space - Variables like
cnt
,i
,j
,a
,b
use constant space
Therefore, the space complexity is O(1)
auxiliary space (not counting the input array).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-one error in binary search range
Pitfall: Using range(nums[-1] - nums[0])
instead of range(nums[-1] - nums[0] + 1)
can miss the maximum possible distance as a valid answer.
Why it happens: The maximum distance between any two elements is exactly nums[-1] - nums[0]
, but range(n)
generates values from 0
to n-1
, excluding n
.
Example scenario:
nums = [1, 100], k = 1 # Only one pair (1, 100) with distance 99 # If we use range(99), bisect_left searches [0, 98] and misses 99
Solution: Always use range(nums[-1] - nums[0] + 1)
to include the maximum distance.
2. Incorrect counting logic for pairs
Pitfall: Counting pairs where nums[i] - nums[j] == dist
instead of nums[i] - nums[j] <= dist
.
Why it happens: Misunderstanding that we need cumulative count (pairs with distance at most dist
) for binary search to work correctly, not exact count.
Incorrect implementation:
def count(dist):
cnt = 0
for i in range(len(nums)):
# Wrong: looking for exact distance
target = nums[i] - dist
if target in nums[:i]:
cnt += 1
return cnt
Solution: Use bisect_left
to find all elements where the difference is <= dist
:
def count(dist):
cnt = 0
for i in range(len(nums)):
j = bisect_left(nums, nums[i] - dist, 0, i)
cnt += i - j # All elements from j to i-1 satisfy the condition
return cnt
3. Forgetting to sort the array
Pitfall: Applying binary search operations on an unsorted array leads to incorrect results.
Why it happens: The counting function relies on the sorted property to use bisect_left
effectively.
Solution: Always sort the array first: nums.sort()
4. Using bisect_right instead of bisect_left in the final search
Pitfall: Using bisect_right
finds the insertion point after the last occurrence, potentially returning a distance larger than necessary.
Why it happens: Confusion about which bisect function to use for finding the k-th smallest.
Example:
# If distances [0, 1, 1, 2, 3] and k = 3 # count(0) = 1, count(1) = 3, count(2) = 4 # bisect_left returns 1 (correct: 3rd smallest is 1) # bisect_right would return 2 (incorrect)
Solution: Use bisect_left
to find the smallest distance where the count is at least k
.
A heap is a ...?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!