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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Binary Search?

This problem maps to Binary Search through a short path in the full flowchart.

Graph?noSorted ormonotonic?yesBinary Search

Binary search locates the optimal start of a length-k window over the sorted array using the closer-end comparison.

Open in Flowchart

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.

Pattern Learn more about Two Pointers, Binary Search, Sorting, Sliding Window and Heap (Priority Queue) patterns.

Uses a heapNew to the pattern? This video explains how heaps and priority queues work.
New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

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 - 1: the last start position whose check can read arr[mid + k] without leaving the array
  • first_true_index = len(arr) - k: the default answer when no checked position is feasible, which is the last window that fits

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: once x - arr[mid] <= arr[mid + k] - x holds, it also holds for every larger mid, because x - arr[mid] can only shrink and arr[mid + k] - x can only grow as mid increases. The template finds the leftmost feasible position. The last start position len(arr) - k is never checked, since its comparison would read arr[len(arr)]; it is the answer when no earlier position is feasible, which is why first_true_index starts there. This also covers k == len(arr), where the loop does not run and the whole array is returned.

Time Complexity: O(log(n - k)) for binary search, where n is the array length.

Space Complexity: O(1) excluding the output array.

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 - 1 = 3, first_true_index = 7 - 3 = 4
  • Checked starting positions: 0, 1, 2, 3 (position 4 is the default)
  • Feasible condition: x - arr[mid] <= arr[mid + k] - x (starting at mid is valid)

Iteration 1:

  • mid = (0 + 3) // 2 = 1
  • Check: x - arr[1] = 5 - 2 = 3, arr[1 + 3] - x = arr[4] - 5 = 5 - 5 = 0
  • Is 3 > 0? Yes → NOT feasible (arr[4]=5 is closer than arr[1]=2)
  • Update: left = 2

Iteration 2:

  • left = 2, right = 3
  • mid = (2 + 3) // 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 3:

  • left = 3, right = 3
  • mid = 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
  • 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.

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 - 1
11        first_true_index = len(arr) - k
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 - 1;
6        int firstTrueIndex = arr.length - k;
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 = (int) arr.size() - k - 1;
7        int firstTrueIndex = (int) arr.size() - k;
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 - 1;
14    let firstTrueIndex = arr.length - k;
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

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.

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

The Pitfall: Searching over every start position 0..len(arr) - k with while left <= right. The check reads arr[mid + k], so when mid = len(arr) - k it reads arr[len(arr)], one past the end.

Incorrect:

left, right = 0, len(arr) - k
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if x - arr[mid] > arr[mid + k] - x:  # IndexError when mid == len(arr) - k
        left = mid + 1
    else:
        first_true_index = mid
        right = mid - 1

For arr = [1, 2, 3], k = 3, the first check reads arr[3]. For arr = [1, 2, 3, 4], k = 2, x = 4, the search moves right until mid = 2 and reads arr[4].

Correct:

left, right = 0, len(arr) - k - 1
first_true_index = len(arr) - k
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 variant while left < right with right = len(arr) - k and right = mid is also correct, because there mid < right, so mid + k stays inside the array.

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. Not Accounting for the Last Window

The Pitfall: Shrinking the search to right = len(arr) - k - 1 but keeping first_true_index = -1. When every checked position is infeasible (for example arr = [1, 2, 3, 4, 5], k = 2, x = 10), the loop ends without setting the index, and the slice arr[-1:1] returns [].

Correct: Initialize first_true_index = len(arr) - k, so the last window is the answer when no earlier start is feasible.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which data structure is used in a depth first search?


Recommended Readings

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

Load More