658. Find K Closest Elements
Problem Description
You are given a sorted integer array arr and two integers k and x. Your task is to find the k integers from the array that are closest to x. The output should be sorted in ascending order.
When determining which integers are "closest" to x, use the following rules:
- An integer
ais closer toxthan an integerbif the absolute difference|a - x|is smaller than|b - x| - If two integers have the same absolute difference from
x(i.e.,|a - x| == |b - x|), then the smaller integer is considered closer
For example, if x = 5 and you're comparing 3 and 7:
|3 - 5| = 2and|7 - 5| = 2(same distance)- Since
3 < 7, the integer3is considered closer to5
The solution approach sorts the entire array based on the distance from x using a custom key function lambda v: abs(v - x). This automatically handles both criteria - first by absolute distance, then by value (since Python's sort is stable). After sorting by distance, the first k elements are taken and then sorted again in ascending order to meet the output requirement.
Intuition
The key insight is that the k closest elements form a contiguous window in the sorted array. Why? Because if we have elements a < b < c and both a and c are in our answer but b is not, then b must be farther from x than both a and c. But since b is between a and c, it's impossible for b to be farther from x than both of them.
This means we need to find the optimal starting position for a window of size k. We can use binary search to find this position efficiently.
For any position mid, we can check whether mid is a valid starting position by comparing the distance from x to the leftmost element (arr[mid]) versus the element just outside the window on the right (arr[mid + k]). If x - arr[mid] > arr[mid + k] - x, it means arr[mid + k] is closer to x than arr[mid], so we should move our window to the right. Otherwise, mid is a valid starting position.
This creates a monotonic pattern: there's a boundary where all positions before it are invalid (the window should start later) and all positions from the boundary onwards are valid. We're looking for the first valid position using the standard binary search template with first_true_index.
Learn more about Two Pointers, Binary Search, Sorting, Sliding Window and Heap (Priority Queue) patterns.
Solution Implementation
1class Solution:
2 def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
3 """
4 Find k closest elements using binary search template.
5 We search for the starting index of the k-element window.
6 Feasible condition: x is closer to arr[mid + k] than to arr[mid],
7 meaning we should NOT start at mid (start position should be > mid).
8 """
9 left = 0
10 right = len(arr) - k
11 first_true_index = -1
12
13 while left <= right:
14 mid = (left + right) // 2
15
16 # Feasible: should we start AFTER position mid?
17 # True when x is closer to arr[mid + k] than to arr[mid]
18 # This means starting at mid would include arr[mid] which is farther
19 if x - arr[mid] > arr[mid + k] - x:
20 # Should start after mid, so first valid start is > mid
21 left = mid + 1
22 else:
23 # Starting at mid is valid (arr[mid] is closer or equal)
24 first_true_index = mid
25 right = mid - 1
26
27 # Return k elements starting from first_true_index
28 return arr[first_true_index:first_true_index + k]
291class Solution {
2 public List<Integer> findClosestElements(int[] arr, int k, int x) {
3 // Binary search for the starting index of the k-element window
4 int left = 0;
5 int right = arr.length - k;
6 int firstTrueIndex = -1;
7
8 while (left <= right) {
9 int mid = left + (right - left) / 2;
10
11 // Check if starting at mid is valid (arr[mid] is closer or equal to arr[mid+k])
12 if (x - arr[mid] > arr[mid + k] - x) {
13 // arr[mid + k] is closer, should start after mid
14 left = mid + 1;
15 } else {
16 // arr[mid] is closer or equal, this is a valid start
17 firstTrueIndex = mid;
18 right = mid - 1;
19 }
20 }
21
22 // Build result list from the k elements starting at firstTrueIndex
23 List<Integer> result = new ArrayList<>();
24 for (int i = firstTrueIndex; i < firstTrueIndex + k; i++) {
25 result.add(arr[i]);
26 }
27
28 return result;
29 }
30}
311class Solution {
2public:
3 vector<int> findClosestElements(vector<int>& arr, int k, int x) {
4 // Binary search for the starting index of the k-element window
5 int left = 0;
6 int right = arr.size() - k;
7 int firstTrueIndex = -1;
8
9 while (left <= right) {
10 int mid = left + (right - left) / 2;
11
12 // Check if starting at mid is valid (arr[mid] is closer or equal to arr[mid+k])
13 if (x - arr[mid] > arr[mid + k] - x) {
14 // arr[mid + k] is closer, should start after mid
15 left = mid + 1;
16 } else {
17 // arr[mid] is closer or equal, this is a valid start
18 firstTrueIndex = mid;
19 right = mid - 1;
20 }
21 }
22
23 // Return k elements starting from firstTrueIndex
24 return vector<int>(arr.begin() + firstTrueIndex, arr.begin() + firstTrueIndex + k);
25 }
26};
271/**
2 * Finds k closest elements to x in a sorted array
3 * Uses binary search template with firstTrueIndex
4 *
5 * @param arr - Sorted array of numbers
6 * @param k - Number of closest elements to find
7 * @param x - Target value to find closest elements to
8 * @returns Array containing k closest elements in sorted order
9 */
10function findClosestElements(arr: number[], k: number, x: number): number[] {
11 // Binary search for the starting index of the k-element window
12 let left = 0;
13 let right = arr.length - k;
14 let firstTrueIndex = -1;
15
16 while (left <= right) {
17 const mid = Math.floor((left + right) / 2);
18
19 // Check if starting at mid is valid (arr[mid] is closer or equal to arr[mid+k])
20 if (x - arr[mid] > arr[mid + k] - x) {
21 // arr[mid + k] is closer, should start after mid
22 left = mid + 1;
23 } else {
24 // arr[mid] is closer or equal, this is a valid start
25 firstTrueIndex = mid;
26 right = mid - 1;
27 }
28 }
29
30 // Return k elements starting from firstTrueIndex
31 return arr.slice(firstTrueIndex, firstTrueIndex + k);
32}
33Solution Approach
We use the standard binary search template with first_true_index to find the optimal starting position for the k-element window.
Binary Search Setup:
left = 0: minimum possible starting positionright = len(arr) - k: maximum possible starting position (window must fit in array)first_true_index = -1: tracks the first valid starting position
Feasible Condition:
For any position mid, we check if mid is a valid starting position:
- Compare
x - arr[mid](distance from x to left boundary) witharr[mid + k] - x(distance from x to element just outside right boundary) - If
x - arr[mid] > arr[mid + k] - x: the element outside the window is closer, somidis NOT valid (move right) - Otherwise:
midis a valid starting position
Binary Search Template:
while left <= right: mid = (left + right) // 2 if x - arr[mid] > arr[mid + k] - x: # Not feasible: arr[mid + k] is closer, should start later left = mid + 1 else: # Feasible: arr[mid] is closer or equal, valid start first_true_index = mid right = mid - 1
Return Result:
Return arr[first_true_index:first_true_index + k] - the k elements starting from the optimal position.
Why This Works:
The feasibility pattern is monotonic. If position i is valid (starting there includes closer elements than starting later), then all positions j > i might also be valid, but we want the leftmost valid position. The template finds exactly this boundary.
Time Complexity: O(log(n - k)) for binary search, where n is the array length.
Space Complexity: O(1) excluding the output array.
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 trace through the binary search template with arr = [1, 2, 3, 4, 5, 6, 7], k = 3, x = 5.
Initial state:
left = 0,right = 7 - 3 = 4,first_true_index = -1- Possible starting positions: 0, 1, 2, 3, 4
- Feasible condition:
x - arr[mid] <= arr[mid + k] - x(starting at mid is valid)
Iteration 1:
mid = (0 + 4) // 2 = 2- Check:
x - arr[2] = 5 - 3 = 2,arr[2 + 3] - x = arr[5] - 5 = 6 - 5 = 1 - Is
2 > 1? Yes → NOT feasible (arr[5]=6 is closer than arr[2]=3) - Update:
left = 3
Iteration 2:
left = 3,right = 4mid = (3 + 4) // 2 = 3- Check:
x - arr[3] = 5 - 4 = 1,arr[3 + 3] - x = arr[6] - 5 = 7 - 5 = 2 - Is
1 > 2? No → Feasible (arr[3]=4 is closer than arr[6]=7) - Update:
first_true_index = 3,right = 2
Iteration 3:
left = 3,right = 2- Loop ends:
left > right
Result:
first_true_index = 3- Return
arr[3:6] = [4, 5, 6]
Verification: Starting at index 3 gives window [4, 5, 6] with distances [1, 0, 1]. Starting at index 2 gives window [3, 4, 5] with distances [2, 1, 0]. Starting at index 4 gives window [5, 6, 7] with distances [0, 1, 2]. The window [4, 5, 6] has the smallest maximum distance, confirming our answer.
Time and Space Complexity
Time Complexity: O(log(n - k) + k)
The binary search runs on a search space of size n - k + 1 (possible starting positions), giving O(log(n - k)) iterations. Each iteration performs constant-time comparisons.
Creating the output slice takes O(k) time to copy k elements.
Overall: O(log(n - k) + k). When k is small relative to n, this is effectively O(log n). When k is close to n, this is O(k).
Space Complexity: O(k)
The algorithm uses only a few variables (left, right, mid, first_true_index) for O(1) auxiliary space. The output array requires O(k) space to store the k closest elements.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
The Pitfall: A common mistake is using while left < right with right = mid instead of the standard template with while left <= right and right = mid - 1.
Incorrect (non-standard template):
while left < right: mid = (left + right) // 2 if x - arr[mid] > arr[mid + k] - x: left = mid + 1 else: right = mid return arr[left:left + k]
Correct (standard template):
first_true_index = -1 while left <= right: mid = (left + right) // 2 if x - arr[mid] > arr[mid + k] - x: left = mid + 1 else: first_true_index = mid right = mid - 1 return arr[first_true_index:first_true_index + k]
The standard template with first_true_index clearly tracks the answer and handles edge cases consistently.
2. Incorrect Distance Comparison
The Pitfall: Using >= instead of > in the comparison, which changes the tiebreaker behavior. When distances are equal, we want to prefer the smaller element (leftmost window).
Incorrect:
if x - arr[mid] >= arr[mid + k] - x: # WRONG: >= excludes equal case left = mid + 1
Correct:
if x - arr[mid] > arr[mid + k] - x: # RIGHT: > includes equal case as valid left = mid + 1
When x - arr[mid] == arr[mid + k] - x, both elements are equidistant from x. The tiebreaker prefers the smaller element, so starting at mid is valid.
3. Off-by-One in Search Bounds
The Pitfall: Setting right = len(arr) - 1 instead of right = len(arr) - k.
Why it's wrong: If we start at position len(arr) - 1, we can't fit k elements. The window would extend beyond the array.
Correct: right = len(arr) - k ensures the k-element window always fits within the array.
4. Not Handling Edge Case: k Equals Array Length
The Pitfall: When k == len(arr), the search space is just one position (index 0), and right = 0.
The Solution: The template handles this correctly:
left = 0,right = 0- First iteration:
mid = 0, checkingarr[0]vsarr[k](butarr[k]is out of bounds!)
Add a guard for this edge case:
if k == len(arr):
return arr[:]
How many times is a tree node visited in a depth first search?
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
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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!