Find K-th Smallest Pair Distance
HardPrompt
The distance of a pair of integers a and b is defined as the absolute difference between a and b.
Given an integer array nums and an integer k, return the kth smallest distance among all the pairs nums[i] and nums[j] where 0 <= i < j < nums.length.
Example 1:
Input: nums = [1,3,1], k = 1
Output: 0
Explanation: Here are all the pairs:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
Then the 1st smallest distance pair is (1,1), and its distance is 0.Example 2:
Input: nums = [1,1,1], k = 2
Output: 0Example 3:
Input: nums = [1,6,1], k = 3
Output: 5
Constraints:
n == nums.length2 <= n <= 1040 <= nums[i] <= 1061 <= k <= n * (n - 1) / 2
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves generating all possible pair distances, storing them in a list, sorting the list, and then picking the k-th element. It is the most straightforward solution but also the least efficient.
Algorithm
- Initialize an empty list,
distances. - Use nested loops to iterate through all unique pairs of indices
(i, j)wherei < j. - For each pair
(nums[i], nums[j]), calculate the distanced = Math.abs(nums[i] - nums[j]). - Add this distance
dto thedistanceslist. - After iterating through all pairs, sort the
distanceslist in ascending order. - The k-th smallest distance is the element at index
k - 1of the sorted list.
Walkthrough
The brute-force method is the most intuitive way to solve this problem. We simply follow the problem definition: find all pair distances, then find the k-th smallest among them.
The algorithm proceeds as follows:
- Initialize an empty list, for example,
distances, to store the absolute differences between pairs of numbers. - Use nested loops to iterate through all unique pairs of indices
(i, j)wherei < j. - For each pair
(nums[i], nums[j]), calculate the distanced = Math.abs(nums[i] - nums[j]). - Add this distance
dto thedistanceslist. - After iterating through all pairs, the
distanceslist will contain allN * (N - 1) / 2pair distances. - Sort the
distanceslist in ascending order. - The k-th smallest distance is the element at index
k - 1of the sorted list.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public int smallestDistancePair(int[] nums, int k) { int n = nums.length; List<Integer> distances = new ArrayList<>(); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { distances.add(Math.abs(nums[i] - nums[j])); } } Collections.sort(distances); return distances.get(k - 1); }}This approach is simple but inefficient due to the large number of pairs, which can be up to approximately 5 * 10^7 for N = 10^4. Sorting this many elements is computationally expensive and will not pass the given constraints.
Complexity
Time
O(N^2 log(N^2)), which simplifies to O(N^2 log N). There are O(N^2) pairs to generate. Sorting a list of size O(N^2) takes O(N^2 log(N^2)) time.
Space
O(N^2), where N is the number of elements in `nums`. This is required to store all the pair distances in a list.
Trade-offs
Pros
Simple to understand and implement.
Cons
Highly inefficient for large inputs due to its high time complexity.
Requires a large amount of memory to store all pair distances, which can lead to Memory Limit Exceeded errors.
This is a highly efficient approach that leverages the properties of the problem. Instead of finding the distances directly, we can binary search for the k-th smallest distance value. The key idea is to guess a distance d and then efficiently count how many pairs have a distance less than or equal to d.
Algorithm
- First, sort the input array
nums. - Define the search space for the binary search. The smallest possible distance is 0, and the largest is
nums[n-1] - nums[0]. Letlow = 0andhigh = nums[n-1] - nums[0]. - Perform a binary search on the range
[low, high]. For eachmidvalue (a potential answer):- Count the number of pairs
(nums[i], nums[j])withnums[j] - nums[i] <= mid. This can be done in O(N) time using a two-pointer/sliding window technique on the sorted array. - Let this count be
count. - If
count >= k, it meansmidmight be our answer, or the actual answer is even smaller. So, we recordmidas a potential answer and try to find a smaller one by settinghigh = mid - 1. - If
count < k, it meansmidis too small to be the k-th distance. We need to look for a larger distance, so we setlow = mid + 1.
- Count the number of pairs
- The last recorded potential answer will be the smallest distance
dfor which there are at leastkpairs with distance<= d, which is the k-th smallest distance.
Walkthrough
A much more efficient solution involves binary searching for the answer. The possible distances are bounded between 0 and max(nums) - min(nums). We can binary search within this range. For any given distance d, we need to be able to efficiently count how many pairs have a distance less than or equal to d. If this count is at least k, then the k-th smallest distance is d or smaller. Otherwise, it must be larger than d.
The countPairs function, which counts pairs with distance <= mid, can be implemented efficiently using a two-pointer (or sliding window) approach on the sorted array in O(N) time.
- Initialize
count = 0and a right pointerright = 0. - Iterate with a left pointer
leftfrom0ton-1. - For each
left, advancerightsuch thatnums[right] - nums[left] <= mid. - The number of valid pairs for the current
leftisright - left - 1. Add this to the totalcount.
import java.util.Arrays; class Solution { public int smallestDistancePair(int[] nums, int k) { Arrays.sort(nums); int n = nums.length; int low = 0; int high = nums[n - 1] - nums[0]; int ans = 0; while (low <= high) { int mid = low + (high - low) / 2; // Count pairs with distance <= mid if (countPairs(nums, mid) >= k) { ans = mid; // This is a potential answer high = mid - 1; // Try for a smaller distance } else { low = mid + 1; // Distance is too small } } return ans; } // Counts pairs with distance <= mid in O(N) private int countPairs(int[] nums, int mid) { int count = 0; int n = nums.length; int right = 0; for (int left = 0; left < n; ++left) { // While the condition is met, move the right pointer while (right < n && nums[right] - nums[left] <= mid) { right++; } // For nums[left], all elements from nums[left+1] to nums[right-1] form a valid pair. // The number of such elements is (right - 1) - (left + 1) + 1 = right - left - 1. count += right - left - 1; } return count; }}Complexity
Time
O(N log N + N log W), where N is the length of `nums` and W is the difference between the maximum and minimum values in `nums`. O(N log N) is for the initial sort. The binary search runs O(log W) times, and each time we do an O(N) scan (the `countPairs` function).
Space
O(log N) or O(N), depending on the implementation of the sorting algorithm. If we are allowed to modify the input array, it can be O(log N) for the recursion stack. Otherwise, O(N) for a copy.
Trade-offs
Pros
Very efficient and passes the time limits for the given constraints.
It cleverly transforms the problem from finding an element to a decision problem, which is a common and powerful technique.
Cons
More complex to understand and implement compared to the brute-force approach.
Requires a good grasp of binary search on the answer and the two-pointer technique.
Solutions
Solution
class Solution {public int smallestDistancePair(int[] nums, int k) { Arrays.sort(nums); int left = 0, right = nums[nums.length - 1] - nums[0]; while (left < right) { int mid = (left + right) >> 1; if (count(mid, nums) >= k) { right = mid; } else { left = mid + 1; } } return left; }private int count(int dist, int[] nums) { int cnt = 0; for (int i = 0; i < nums.length; ++i) { int left = 0, right = i; while (left < right) { int mid = (left + right) >> 1; int target = nums[i] - dist; if (nums[mid] >= target) { right = mid; } else { left = mid + 1; } } cnt += i - left; } return cnt; }}/** * @param {number[]} nums * @param {number} k * @return {number} */ function smallestDistancePair( nums, k,) { nums.sort((a, b) => a - b); const n = nums.length; let left = 0, right = nums[n - 1] - nums[0]; while (left < right) { const mid = (left + right) >> 1; let count = 0, i = 0; for (let j = 0; j < n; j++) { while (nums[j] - nums[i] > mid) { i++; } count += j - i; } if (count >= k) { right = mid; } else { left = mid + 1; } } return left;}class Solution {public: int smallestDistancePair(vector<int> &nums, int k) { sort(nums.begin(), nums.end()); int left = 0, right = nums.back() - nums.front(); while (left < right) { int mid = (left + right) >> 1; if (count(mid, k, nums) >= k) right = mid; else left = mid + 1; } return left; } int count(int dist, int k, vector<int> &nums) { int cnt = 0; for (int i = 0; i < nums.size(); ++i) { int target = nums[i] - dist; int j = lower_bound(nums.begin(), nums.end(), target) - nums.begin(); cnt += i - j; } return cnt; }};class Solution: def smallestDistancePair(self, nums: List[int], k: int) -> int: 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 nums . sort() return bisect_left(range(nums[- 1] - nums[0]), k, key=count)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.