Facebook Pixel

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 to x than an integer b 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 integer 3 is considered closer to 5

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 Evaluator

Example 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 0
  • 4 with distance 1
  • 6 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:

  1. The first sort using a custom key function lambda v: abs(v - x) takes O(n log n) time, where n is the length of the input array. This sorts all elements by their absolute distance from x.
  2. The second sort on the slice arr[:k] takes O(k log k) time to sort the k 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:

  1. The sort() method with a custom key function creates a temporary array to store the key values abs(v - x) for all n elements, requiring O(n) space.
  2. The slice arr[:k] creates a new list of size k, requiring O(k) space.
  3. The sorted() function creates another new list of size k, requiring O(k) space.
  4. 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])
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More