Facebook Pixel

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] and k = 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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]) where j < i and nums[i] - nums[j] <= dist
  • This means nums[j] >= nums[i] - dist (which is stored as a)
  • bisect_left(nums, a, 0, i) finds the leftmost index j in the range [0, i) where nums[j] >= a
  • The count of valid pairs with nums[i] as the larger element is i - 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 where count(dist) >= k
  • bisect_left finds the smallest distance d such that the number of pairs with distance <= d is at least k

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 takes O(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 Evaluator

Example 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: Check nums[j] ≥ 1-2 = -1. All elements before index 1 satisfy this (index 0), so 1 pair
    • For nums[2]=6: Check nums[j] ≥ 6-2 = 4. No elements before index 2 satisfy this, so 0 pairs
    • Total: 1 pair
  • 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: Check nums[j] ≥ 1-4 = -3. Element at index 0 satisfies this, so 1 pair
    • For nums[2]=6: Check nums[j] ≥ 6-4 = 2. No elements before index 2 satisfy this, so 0 pairs
    • Total: 1 pair
  • 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: Check nums[j] ≥ 1-5 = -4. Element at index 0 satisfies this, so 1 pair
    • For nums[2]=6: Check nums[j] ≥ 6-5 = 1. Elements at indices 0 and 1 both equal 1, so 2 pairs
    • Total: 3 pairs
  • 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) where n is the length of nums
  • The binary search on the range [0, nums[-1] - nums[0]] takes O(log(max_diff)) iterations, where max_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 takes O(log n)
    • Therefore, count function takes O(n log n) time
  • 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 with range() in Python 3 creates an iterator (not a list), so it uses O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More