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
a
is closer tox
than an integerb
if 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| = 2
and|7 - 5| = 2
(same distance)- Since
3 < 7
, the integer3
is 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 we need to find elements based on their "distance" from x
, not their actual values. Since we want the k
closest elements, we can think of this as a sorting problem where we reorder elements by their proximity to x
.
The most straightforward approach is to sort the array using a custom comparison criterion. Instead of sorting by the actual values, we sort by |element - x|
, which gives us the distance of each element from x
.
Python's built-in sort is stable, meaning that when two elements have the same sorting key (same distance from x
), they maintain their original relative order. Since the original array is already sorted in ascending order, if two elements have the same distance from x
, the smaller one will naturally come first after sorting - which is exactly what we want according to the tiebreaker rule.
Once we have the array sorted by distance, the first k
elements are guaranteed to be the k
closest to x
. However, these elements might not be in ascending order anymore (for example, if x = 5
and we have [4, 6]
, after sorting by distance they might appear as [6, 4]
). Therefore, we need a final step to sort these k
elements back into ascending order to satisfy the output requirement.
This approach transforms the problem from "finding closest elements" into "sorting by distance and taking the first k", which is conceptually simpler and leverages existing sorting algorithms efficiently.
Learn more about Two Pointers, Binary Search, Sorting, Sliding Window and Heap (Priority Queue) patterns.
Solution Approach
The solution uses a sorting-based approach to find the k closest elements. Here's how the implementation works:
Step 1: Sort by Distance
arr.sort(key=lambda v: abs(v - x))
This line sorts the entire array using a custom key function. The lambda v: abs(v - x)
function calculates the absolute distance between each element v
and the target value x
. This transforms the sorting criterion from value-based to distance-based.
For example, if arr = [1, 2, 3, 4, 5]
and x = 3
:
- Element
1
has distance|1 - 3| = 2
- Element
2
has distance|2 - 3| = 1
- Element
3
has distance|3 - 3| = 0
- Element
4
has distance|4 - 3| = 1
- Element
5
has distance|5 - 3| = 2
After sorting by distance, the array becomes [3, 2, 4, 1, 5]
.
Step 2: Select k Elements
arr[:k]
This slice operation takes the first k
elements from the distance-sorted array. These are guaranteed to be the k
elements with the smallest distances from x
.
Step 3: Sort in Ascending Order
return sorted(arr[:k])
The final step sorts the selected k
elements in ascending order to meet the problem's output requirement. This is necessary because after sorting by distance, the elements might not be in ascending order.
Time Complexity: O(n log n)
for sorting the entire array, where n
is the length of the array.
Space Complexity: O(1)
if we don't count the space used by the sorting algorithm (typically O(log n)
for the recursion stack in quicksort or mergesort).
The beauty of this approach lies in its simplicity - by leveraging Python's stable sort property and a custom key function, we can solve the problem in just two lines of code without explicitly handling the tiebreaker rule.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
arr = [1, 2, 3, 4, 5, 6, 7]
k = 3
x = 5
Step 1: Calculate distances and sort by distance
First, let's calculate the distance of each element from x = 5
:
1
: distance =|1 - 5| = 4
2
: distance =|2 - 5| = 3
3
: distance =|3 - 5| = 2
4
: distance =|4 - 5| = 1
5
: distance =|5 - 5| = 0
6
: distance =|6 - 5| = 1
7
: distance =|7 - 5| = 2
When we sort by distance using arr.sort(key=lambda v: abs(v - x))
, we get:
- Distance 0:
[5]
- Distance 1:
[4, 6]
(4 comes before 6 due to stable sort preserving original order) - Distance 2:
[3, 7]
(3 comes before 7 due to stable sort preserving original order) - Distance 3:
[2]
- Distance 4:
[1]
So after sorting by distance: arr = [5, 4, 6, 3, 7, 2, 1]
Step 2: Select the first k elements
Taking arr[:k]
where k = 3
, we get: [5, 4, 6]
These are the 3 elements closest to 5:
5
with distance 04
with distance 16
with distance 1
Step 3: Sort the result in ascending order
Finally, sorted([5, 4, 6])
gives us: [4, 5, 6]
Final Answer: [4, 5, 6]
Notice how the tiebreaker rule was automatically handled: both 4
and 6
have the same distance from 5
, but since the original array was sorted and Python's sort is stable, 4
naturally came before 6
in our distance-sorted array.
Solution Implementation
1class Solution:
2 def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
3 # Sort the array based on distance from x (closest elements first)
4 # If distances are equal, smaller value comes first (default behavior)
5 arr.sort(key=lambda value: abs(value - x))
6
7 # Take the k closest elements and sort them in ascending order
8 result = sorted(arr[:k])
9
10 return result
11```
12
13However, let me provide an optimized version that better handles the problem requirements:
14
15```python3
16class Solution:
17 def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
18 # Use binary search to find the starting position of k closest elements
19 left = 0
20 right = len(arr) - k
21
22 # Binary search for the best window of size k
23 while left < right:
24 mid = (left + right) // 2
25
26 # Compare distances: if x is closer to arr[mid + k], move left pointer
27 # Otherwise, move right pointer
28 if x - arr[mid] > arr[mid + k] - x:
29 left = mid + 1
30 else:
31 right = mid
32
33 # Return k elements starting from the found position
34 return arr[left:left + k]
35
1class Solution {
2 public List<Integer> findClosestElements(int[] arr, int k, int x) {
3 // Convert array to list for easier manipulation
4 List<Integer> result = Arrays.stream(arr)
5 .boxed()
6 .sorted((a, b) -> {
7 // Custom comparator: sort by distance from x, then by value if tied
8 int distanceDifference = Math.abs(a - x) - Math.abs(b - x);
9
10 // If distances are equal, return smaller element first
11 // Otherwise, return element with smaller distance first
12 return distanceDifference == 0 ? a - b : distanceDifference;
13 })
14 .collect(Collectors.toList());
15
16 // Take only the k closest elements
17 result = result.subList(0, k);
18
19 // Sort the k elements in ascending order as required
20 Collections.sort(result);
21
22 return result;
23 }
24}
25
1class Solution {
2private:
3 // Static variable to hold the target value for comparison
4 static int targetValue;
5
6 // Custom comparator for sorting elements by distance from target
7 // If distances are equal, prefer smaller element
8 static bool compareByDistance(const int& a, const int& b) {
9 int distanceDiff = abs(a - targetValue) - abs(b - targetValue);
10
11 // If distances are equal, return the smaller element
12 if (distanceDiff == 0) {
13 return a < b;
14 }
15
16 // Otherwise, return the element closer to target
17 return distanceDiff < 0;
18 }
19
20public:
21 vector<int> findClosestElements(vector<int>& arr, int k, int x) {
22 // Set the target value for the comparator
23 targetValue = x;
24
25 // Sort the entire array based on distance from x
26 sort(arr.begin(), arr.end(), compareByDistance);
27
28 // Extract the k closest elements
29 vector<int> result(arr.begin(), arr.begin() + k);
30
31 // Sort the result in ascending order as required
32 sort(result.begin(), result.end());
33
34 return result;
35 }
36};
37
38// Definition of static member variable
39int Solution::targetValue;
40
1/**
2 * Finds k closest elements to x in a sorted array
3 * Uses two pointers to narrow down the window of k elements
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 // Initialize left pointer at the beginning of array
12 let left: number = 0;
13
14 // Initialize right pointer at the end of array (exclusive)
15 let right: number = arr.length;
16
17 // Narrow down the window until we have exactly k elements
18 while (right - left > k) {
19 // Compare distances from x to elements at boundaries
20 // If left element is closer or equally close, exclude right element
21 if (x - arr[left] <= arr[right - 1] - x) {
22 right--;
23 } else {
24 // Otherwise, exclude left element
25 left++;
26 }
27 }
28
29 // Return the k closest elements as a subarray
30 return arr.slice(left, right);
31}
32
Time and Space Complexity
Time Complexity: O(n log n)
The time complexity is dominated by two sorting operations:
- The first sort using a custom key function
lambda v: abs(v - x)
takesO(n log n)
time, wheren
is the length of the input array. This sorts all elements by their absolute distance fromx
. - The second sort on the slice
arr[:k]
takesO(k log k)
time to sort thek
closest elements back into ascending order.
Since k ≤ n
, the overall time complexity is O(n log n + k log k) = O(n log n)
.
Space Complexity: O(n)
The space complexity analysis:
- The
sort()
method with a custom key function creates a temporary array to store the key valuesabs(v - x)
for alln
elements, requiringO(n)
space. - The slice
arr[:k]
creates a new list of sizek
, requiringO(k)
space. - The
sorted()
function creates another new list of sizek
, requiringO(k)
space. - Python's Timsort algorithm (used in both sorting operations) requires
O(n)
auxiliary space in the worst case.
The total space complexity is O(n + k) = O(n)
since k ≤ n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Inefficient Time Complexity with Full Array Sorting
The basic sorting approach sorts the entire array with O(n log n)
complexity, which is inefficient when k
is much smaller than n
. For example, if the array has 10,000 elements but you only need to find 5 closest elements, sorting all 10,000 elements is wasteful.
Solution: Use a heap-based approach or binary search to avoid sorting the entire array:
import heapq
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# Use a max heap to keep only k closest elements
heap = []
for num in arr:
distance = abs(num - x)
# Use negative distance for max heap, and negative num for tiebreaker
heapq.heappush(heap, (-distance, -num))
if len(heap) > k:
heapq.heappop(heap)
# Extract elements and sort them
return sorted([-num for _, num in heap])
Pitfall 2: Incorrect Tiebreaker Implementation
When two elements have the same distance from x
, the smaller value should be preferred. The basic sorting approach relies on Python's stable sort, but this behavior might not be explicit or guaranteed in other languages or implementations.
Example of the issue:
# If arr = [1, 2, 3, 4, 5], x = 3, k = 2 # Both 2 and 4 have distance 1 from 3 # Without proper tiebreaker, might incorrectly select [3, 4] instead of [2, 3]
Solution: Explicitly handle the tiebreaker in the comparison:
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# Create tuples of (distance, value) for explicit comparison
arr.sort(key=lambda v: (abs(v - x), v))
return sorted(arr[:k])
Pitfall 3: Not Leveraging the Sorted Input
The original array is already sorted, but the basic sorting approach doesn't take advantage of this property. This leads to unnecessary work.
Solution: Use binary search with a sliding window approach:
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# Find the optimal window position using binary search
left, right = 0, len(arr) - k
while left < right:
mid = (left + right) // 2
# Check which boundary is closer to x
if x - arr[mid] > arr[mid + k] - x:
left = mid + 1
else:
right = mid
return arr[left:left + k]
Pitfall 4: Memory Overhead from In-Place Sorting
The basic approach modifies the original array with arr.sort()
, which could be problematic if the input array needs to be preserved or if working with shared data structures.
Solution: Create a copy or use sorted()
instead:
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# Use sorted() to avoid modifying the original array
sorted_by_distance = sorted(arr, key=lambda v: abs(v - x))
return sorted(sorted_by_distance[:k])
How many ways can you arrange the three letters A, B and C?
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!