A clear explanation of finding the k closest elements to a target using binary search and a sliding window.
Problem Restatement
We are given:
| Input | Meaning |
|---|---|
arr | A sorted integer array |
k | Number of elements to return |
x | Target value |
We must return the k elements closest to x.
The returned elements must remain sorted in ascending order.
If two numbers are equally close, we prefer the smaller number.
The problem defines closeness using:
|a - x| < |b - x|or, when distances are equal:
a < bInput and Output
| Item | Meaning |
|---|---|
| Input | Sorted array arr, integer k, integer x |
| Output | A sorted list of k closest elements |
| Tie-breaking | Prefer the smaller value |
| Array property | arr is already sorted |
Example function shape:
def findClosestElements(arr: list[int], k: int, x: int) -> list[int]:
...Examples
Consider:
arr = [1, 2, 3, 4, 5]
k = 4
x = 3The closest elements to 3 are:
[1, 2, 3, 4]Distances:
| Value | Distance to 3 |
|---|---|
1 | 2 |
2 | 1 |
3 | 0 |
4 | 1 |
5 | 2 |
The four closest elements are:
[1, 2, 3, 4]Another example:
arr = [1, 2, 3, 4, 5]
k = 4
x = -1Since x is left of all elements, the closest values are the first four elements:
[1, 2, 3, 4]Another example:
arr = [1, 1, 2, 3, 4, 5]
k = 4
x = 3The answer is:
[1, 2, 3, 4]Even though both 1 and 5 are distance 2 away, we prefer the smaller value 1.
First Thought: Sort by Distance
A direct approach is:
- Compute the distance of every element to
x. - Sort by:
- Distance
- Value itself for tie-breaking
- Take the first
kelements. - Sort them again in ascending order.
This works, but it ignores an important fact:
arr is already sortedWe should use that structure.
Key Insight
The final answer is always a continuous subarray.
Suppose we choose k closest elements.
Because the array is sorted, if two elements belong to the answer, then every element between them must also belong to the answer.
So instead of choosing arbitrary elements, we only need to find:
the best window of length kIf the array length is n, then the starting index can range from:
0to:
n - kWe can binary search for the correct starting position.
Comparing Two Windows
Suppose we are deciding between two neighboring windows:
arr[left : left + k]and:
arr[left + 1 : left + 1 + k]The only difference is:
| First window | Second window |
|---|---|
Includes arr[left] | Includes arr[left + k] |
So we compare:
x - arr[left]and:
arr[left + k] - xIf the left side is larger, then the right endpoint is closer, so we should move the window right.
Otherwise, keep the current window.
Algorithm
Use binary search on the starting index of the window.
Initialize:
left = 0
right = len(arr) - kWhile:
left < rightcompute:
mid = (left + right) // 2Then compare:
x - arr[mid]and:
arr[mid + k] - xIf the left side is larger:
left = mid + 1Otherwise:
right = midAt the end, left is the best window start.
Return:
arr[left:left + k]Correctness
The algorithm searches over all possible windows of length k.
For any neighboring windows:
arr[i : i+k]and:
arr[i+1 : i+1+k]the only difference is that the first contains arr[i], while the second contains arr[i+k].
If:
x - arr[i] > arr[i+k] - xthen arr[i+k] is closer to x than arr[i], so the better window must start to the right.
Otherwise, the current window is at least as good as the right neighbor. In the tie case, the smaller element should be preferred, so the left window remains better.
This comparison allows binary search because once the optimal window lies to the right, all earlier windows are worse.
The binary search eventually finds the unique smallest starting index of the optimal window. Returning the subarray of length k starting there gives exactly the k closest elements in sorted order.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(log(n-k) + k) | Binary search plus output construction |
| Space | O(1) extra | Ignoring the returned list |
The returned slice itself contains k elements.
Implementation
from typing import List
class Solution:
def findClosestElements(
self,
arr: List[int],
k: int,
x: int,
) -> List[int]:
left = 0
right = len(arr) - k
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]Code Explanation
The search space is the possible starting positions of a window of size k.
left = 0
right = len(arr) - kIf the array has length n, then the last valid window starts at:
n - kWe binary search this range.
while left < right:At each step:
mid = (left + right) // 2We compare the left endpoint removed from the current window with the new right endpoint added by shifting the window right.
if x - arr[mid] > arr[mid + k] - x:If this condition is true, the right endpoint is closer, so the better window starts later.
left = mid + 1Otherwise, the current window is still optimal or tied in a favorable way.
right = midFinally:
return arr[left:left + k]returns the optimal window.
Testing
def run_tests():
s = Solution()
assert s.findClosestElements(
[1, 2, 3, 4, 5],
4,
3,
) == [1, 2, 3, 4]
assert s.findClosestElements(
[1, 2, 3, 4, 5],
4,
-1,
) == [1, 2, 3, 4]
assert s.findClosestElements(
[1, 2, 3, 4, 5],
4,
10,
) == [2, 3, 4, 5]
assert s.findClosestElements(
[1, 1, 2, 3, 4, 5],
4,
3,
) == [1, 2, 3, 4]
assert s.findClosestElements(
[1],
1,
0,
) == [1]
assert s.findClosestElements(
[1, 2, 3, 4],
2,
2,
) == [1, 2]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Standard centered target | Basic behavior |
x smaller than all values | Chooses leftmost window |
x larger than all values | Chooses rightmost window |
| Duplicate values | Confirms tie handling |
| Single-element array | Smallest valid input |
| Exact midpoint tie | Confirms smaller values are preferred |