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 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]
29
1class 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}
31
1class 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};
27
1/**
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}
33

Solution 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 position
  • right = 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) with arr[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, so mid is NOT valid (move right)
  • Otherwise: mid is 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 Evaluator

Example 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 = 4
  • mid = (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, checking arr[0] vs arr[k] (but arr[k] is out of bounds!)

Add a guard for this edge case:

if k == len(arr):
    return arr[:]
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More