Find K Closest Elements
MedPrompt
Given a sorted integer array arr, two integers k and x, return the k closest integers to x in the array. The result should also be sorted in ascending order.
An integer a is closer to x than an integer b if:
|a - x| < |b - x|, or|a - x| == |b - x|anda < b
Example 1:
Input: arr = [1,2,3,4,5], k = 4, x = 3
Output: [1,2,3,4]
Example 2:
Input: arr = [1,1,2,3,4,5], k = 4, x = -1
Output: [1,1,2,3]
Constraints:
1 <= k <= arr.length1 <= arr.length <= 104arris sorted in ascending order.-104 <= arr[i], x <= 104
Approaches
4 approaches with complexity analysis and trade-offs.
The most straightforward approach is to sort the entire array based on a custom rule. The rule is the distance of each element from x. If two elements have the same distance, the smaller element is considered closer. This method is easy to conceptualize but not the most performant.
Algorithm
-
- Convert the
int[] arrto aList<Integer>to useCollections.sort.
- Convert the
-
- Sort the list using
Collections.sortand a custom comparator.
- Sort the list using
-
- The comparator
(a, b)first comparesMath.abs(a - x)withMath.abs(b - x).
- The comparator
-
- If the absolute differences are equal, it compares
aandbto handle the tie-breaker (a < bis closer).
- If the absolute differences are equal, it compares
-
- After sorting, the
kclosest elements are the firstkelements in the list.
- After sorting, the
-
- Create a new list containing these first
kelements.
- Create a new list containing these first
-
- Sort this new list of
kelements in ascending order, as the custom sort does not guarantee numerical order for elements with different distances.
- Sort this new list of
-
- Return the final sorted list.
Walkthrough
This approach leverages Java's built-in sorting capabilities. We first convert the input array arr into a List<Integer> because the Collections.sort method works with lists and allows for custom comparators. The core of this method is the custom Comparator. It compares two numbers, a and b, based on their absolute difference from x. If |a - x| is not equal to |b - x|, the one with the smaller difference comes first. If the differences are equal, the problem states that the smaller number (a < b) should come first. After sorting the entire list with this logic, the k closest elements will be at the beginning of the list. We then take a sublist of the first k elements and sort it again in natural ascending order before returning it.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { List<Integer> list = new ArrayList<>(); for (int num : arr) { list.add(num); } // Sort based on distance from x Collections.sort(list, (a, b) -> { int diffA = Math.abs(a - x); int diffB = Math.abs(b - x); if (diffA != diffB) { return diffA - diffB; } else { return a - b; } }); // Take the first k elements List<Integer> result = new ArrayList<>(list.subList(0, k)); // Sort the result in ascending order Collections.sort(result); return result; }}Complexity
Time
O(N log N) - The dominant operation is sorting the entire array of N elements. Sorting the final k elements takes an additional O(k log k), but this is overshadowed by O(N log N).
Space
O(N) - We need to create a list of Integer objects from the primitive `int` array, which requires space proportional to the number of elements N.
Trade-offs
Pros
Simple to understand and implement using standard library functions.
Cons
Highly inefficient for large arrays as it sorts the entire array, even though we only need
kelements.Requires extra space to convert the primitive array to a list of objects.
A more optimized approach uses a max heap (implemented with a PriorityQueue in Java) to keep track of the k closest elements found so far. This avoids sorting the entire array. We iterate through the array, and for each element, we decide if it's closer to x than the farthest element currently in our set of k closest elements, which is always at the top of the max heap.
Algorithm
-
- Initialize a max
PriorityQueue(max heap). The comparator should place the element "farthest" fromxat the top.
- Initialize a max
-
- The comparator
(a, b)should first compare|a - x|with|b - x|. If they are different, the one with the larger distance has higher priority. If they are equal, the larger numberbhas higher priority.
- The comparator
-
- Iterate through each element
numin the input arrayarr.
- Iterate through each element
-
- Add
numto the priority queue.
- Add
-
- If the size of the priority queue exceeds
k, remove the top element usingpoll().
- If the size of the priority queue exceeds
-
- After the loop, the priority queue holds the
kclosest elements.
- After the loop, the priority queue holds the
-
- Convert the priority queue to a list and sort it in ascending order.
-
- Return the sorted list.
Walkthrough
This method is a classic solution for "Top K" problems. We maintain a max heap of size k. The heap is ordered based on the "closeness" criteria, but in reverse: the element at the top of the heap is the one that is least close among the k elements currently in the heap.
As we iterate through the input array arr, we add each element to the heap. If the heap's size becomes k + 1, we remove the top element (poll()), effectively discarding the element that is farthest from x. After iterating through all N elements, the heap contains the k elements from the original array that are closest to x. Finally, we extract these elements from the heap and sort them in ascending order to meet the output requirement.
import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.PriorityQueue; class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { // Max heap PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> { int diffA = Math.abs(a - x); int diffB = Math.abs(b - x); if (diffA != diffB) { return diffB - diffA; // Farthest distance has higher priority } else { return b - a; // Larger element has higher priority } }); for (int num : arr) { maxHeap.offer(num); if (maxHeap.size() > k) { maxHeap.poll(); } } List<Integer> result = new ArrayList<>(maxHeap); Collections.sort(result); return result; }}Complexity
Time
O(N log k) - We iterate through N elements. For each element, we perform a heap operation (add/remove) which takes O(log k) time. Sorting the final result takes O(k log k).
Space
O(k) - The heap stores at most k+1 elements.
Trade-offs
Pros
More efficient than sorting the whole array, especially when
kis much smaller thanN.A standard pattern for solving "Top K" problems.
Cons
Slightly more complex to implement the comparator logic for the max heap compared to the simple sorting approach.
Still processes all N elements, which is less efficient than binary search-based methods.
Since the input array is sorted, we can leverage binary search to quickly find the element closest to x or its insertion point. From this 'center' point, we can use two pointers, one moving left and one moving right, to expand our window and collect the k closest elements one by one.
Algorithm
-
- Use binary search to find the index
rightof the first element that is greater than or equal tox. Letleft = right - 1.
- Use binary search to find the index
-
- Initialize a result list, preferably a
LinkedListorArrayDequefor efficient additions at both ends.
- Initialize a result list, preferably a
-
- Repeat
ktimes to collectkelements:
- Repeat
-
- Check for boundary conditions: If
leftis out of bounds (< 0), we must take from the right. Ifrightis out of bounds (>= arr.length), we must take from the left.
- Check for boundary conditions: If
-
- If both pointers are valid, compare the distances:
x - arr[left]vsarr[right] - x.
- If both pointers are valid, compare the distances:
-
- If the left element is closer or equidistant (
x - arr[left] <= arr[right] - x), addarr[left]to the front of the result list and decrementleft.
- If the left element is closer or equidistant (
-
- Otherwise, add
arr[right]to the back of the result list and incrementright.
- Otherwise, add
-
- After
kiterations, the deque will contain thekclosest elements in sorted order.
- After
-
- Convert the deque to a list and return it.
Walkthrough
This efficient approach takes full advantage of the sorted input array. First, we perform a binary search to locate the index of x, or if x is not present, the index where x would be inserted to maintain the sorted order. This gives us a starting point. We initialize two pointers, left and right, on either side of this starting point.
Then, we iterate k times. In each iteration, we compare the elements pointed to by left and right to see which is closer to x. We add the closer element to our result and move the corresponding pointer inward (i.e., left-- or right++). We must be careful to handle the edge cases where one of the pointers moves out of the array's bounds. In such a case, we simply take elements from the pointer that is still in bounds. By adding elements from the left to the front of a deque and elements from the right to the back, we can build the final sorted list directly.
import java.util.ArrayList;import java.util.LinkedList;import java.util.List; class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { int n = arr.length; // Find the insertion point for x int right = 0; while(right < n && arr[right] < x) { right++; } int left = right - 1; LinkedList<Integer> result = new LinkedList<>(); while (k-- > 0) { if (left < 0) { result.addLast(arr[right++]); } else if (right >= n) { result.addFirst(arr[left--]); } else if (x - arr[left] <= arr[right] - x) { result.addFirst(arr[left--]); } else { result.addLast(arr[right++]); } } return result; }}Complexity
Time
O(log N + k) - Binary search takes O(log N) to find the starting pointers (or a linear scan as in the code, which is O(N) in worst case, but can be replaced by `Arrays.binarySearch` for O(log N)). The two-pointer expansion takes O(k) time.
Space
O(k) - To store the resulting k elements in the deque.
Trade-offs
Pros
Very efficient with O(log N + k) time complexity.
Avoids processing or sorting the entire array.
Combines two powerful techniques: binary search and two pointers.
Cons
The logic for handling pointers and boundary conditions can be tricky to implement correctly.
Involves multiple steps: binary search, then a loop with pointer manipulation.
This is arguably the most elegant and efficient approach. We observe that the k closest elements will always form a contiguous subarray of the sorted arr. Instead of finding the elements one by one, we can directly find the starting index of this k-element subarray using a single, clever binary search.
Algorithm
-
- The result will be a contiguous subarray of
arrof lengthk. The goal is to find the optimal starting index of this subarray.
- The result will be a contiguous subarray of
-
- The search space for the starting index is
[0, arr.length - k].
- The search space for the starting index is
-
- Initialize two pointers,
low = 0andhigh = arr.length - k.
- Initialize two pointers,
-
- Perform a binary search on this range of indices.
-
- In each step, calculate
mid = low + (high - low) / 2. Thismidis a potential starting index.
- In each step, calculate
-
- Compare the element
x's distance toarr[mid](the start of the window) andarr[mid + k](the first element outside the window to the right).
- Compare the element
-
- If
xis farther fromarr[mid]than fromarr[mid + k](i.e.,x - arr[mid] > arr[mid + k] - x), it means the optimal window is to the right. So, we shrink the search space to[mid + 1, high]by settinglow = mid + 1.
- If
-
- Otherwise, the current window
arr[mid...mid+k-1]is a better candidate than any window to its right. The optimal window could be atmidor to its left. So, we shrink the search space to[low, mid]by settinghigh = mid.
- Otherwise, the current window
-
- When the loop terminates (
low == high),lowis the optimal starting index.
- When the loop terminates (
-
- Create and return a list of
kelements fromarrstarting at indexlow.
- Create and return a list of
Walkthrough
The core idea is to reframe the problem from finding k elements to finding the single best starting index for a window of size k. Since the result must be a contiguous block of k elements from the sorted array arr, we just need to find where that block begins. The possible starting indices range from 0 to n-k.
We can binary search over this range of possible start indices. For a given mid index, we consider the window arr[mid...mid+k-1]. To decide whether to move our search left or right, we check if we can form a better window by shifting it one position to the right. A better window would be arr[mid+1...mid+k]. This is equivalent to comparing the element we would drop, arr[mid], with the element we would gain, arr[mid+k]. If x is closer to arr[mid+k] than arr[mid], it means we should favor the right side, so we move our search space by setting low = mid + 1. Otherwise, the current window starting at mid is optimal or better than any to its right, so we set high = mid. The loop terminates when low and high converge to the single best starting index.
import java.util.ArrayList;import java.util.List; class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { int low = 0; int high = arr.length - k; while (low < high) { int mid = low + (high - low) / 2; // If x is closer to arr[mid+k] than arr[mid], // it means our window is too far to the left. // We should move the window to the right. if (x - arr[mid] > arr[mid + k] - x) { low = mid + 1; } else { // Otherwise, the current window is better or equal, // so the optimal window can be at `mid` or to its left. high = mid; } } // The result is the subarray of size k starting at `low` List<Integer> result = new ArrayList<>(k); for (int i = 0; i < k; i++) { result.add(arr[low + i]); } return result; }}Complexity
Time
O(log(N-k) + k) - The binary search is performed on a range of `N-k` possible indices, taking O(log(N-k)) time. Creating the final list from the subarray takes O(k) time.
Space
O(k) - Required to create the output list. The algorithm itself uses O(1) extra space.
Trade-offs
Pros
The most efficient and concise solution.
Finds the result window directly without expanding one by one.
Guarantees an O(log(N-k)) search time.
Cons
The logic of the binary search condition, comparing
arr[mid]andarr[mid+k], can be non-obvious at first glance.
Solutions
Solution
class Solution { public List < Integer > findClosestElements ( int [] arr , int k , int x ) { int left = 0 ; int right = arr . length - k ; while ( left < right ) { int mid = ( left + right ) >> 1 ; if ( x - arr [ mid ] <= arr [ mid + k ] - x ) { right = mid ; } else { left = mid + 1 ; } } List < Integer > ans = new ArrayList <>(); for ( int i = left ; i < left + k ; ++ i ) { ans . add ( arr [ i ]); } return ans ; } }class Solution { public: vector < int > findClosestElements ( vector < int >& arr , int k , int x ) { int left = 0 , right = arr . size () - k ; while ( left < right ) { int mid = ( left + right ) >> 1 ; if ( x - arr [ mid ] <= arr [ mid + k ] - x ) right = mid ; else left = mid + 1 ; } return vector < int > ( arr . begin () + left , arr . begin () + left + k ); } };class Solution : def findClosestElements ( self , arr : List [ int ], k : int , x : int ) -> List [ int ]: left , right = 0 , len ( arr ) - k while left < right : mid = ( left + right ) >> 1 if x - arr [ mid ] <= arr [ mid + k ] - x : right = mid else : left = mid + 1 return arr [ left : left + k ]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.