## Find K Closest Elements

###### DESCRIPTION (credit Leetcode.com)

Given a sorted array nums, a target value target, and an integer k, find the k closest elements to target in the array, where "closest" is the absolute difference between each element and target. Return these elements in array, sorted in ascending order.

###### EXAMPLES

**Example 1:**

Inputs:

`nums = [-1, 0, 1, 4, 6] target = 1 k = 3`

Output:

`[-1, 0, 1]`

**Explanation:** -1 is 2 away from 1, 0 is 1 away from 1, and 1 is 0 away from 1. All other elements are more than 2 away. Since we need to return the elements in ascending order, the answer is [-1, 0, 1]

**Example 2:**

Inputs:

`nums = [5, 6, 7, 8, 9] target = 10 k = 2`

Output:

`[8, 9]`

Run your code to see results here

## Explanation

### Approach 1: Sorting

The simplest approach is to calculate the distance of each element from the target and to sort the elements based on that distance. This approach has a time complexity of O(n log n) where n is the number of points in the array, and a space complexity of O(n) (to store the sorted array of distances).

### Approach 2: Max-Heap

This problem can be solved using a similar approach to the one used to solve Kth Largest Element in an Array, with the key difference being that we need to find the *k closest elements* to the target, rather than the *k largest elements*. Since we are looking for the k smallest elements, we need a **max-heap**, rather than a min-heap.

By default, python's heapq module implements a min-heap, but we can make it behave like a max-heap by negating the values of everything we push onto it.

First, we push the first k elements to the heap by storing a tuple containing the *negative of the distance* of the element from the target, and the element itself. After that is finished, our heap contains the k closest elements to the target that we've seen so far, with the element furthest from the target at the root of the heap.

import heapqdef k_closest(nums, k, target):heap = []for num in nums:distance = abs(num - target)if len(heap) < k:heapq.heappush(heap, (-distance, num))elif distance < -heap[0][0]:heapq.heappushpop(heap, (-distance, num))distances = [pair[1] for pair in heap]distances.sort()return distances

For each element after the first k, we calculate the distance from the target and compare it with the root of the heap. If the current element is closer to the target than the root of the heap, we pop the root and push a tuple containing the negative distance of the current element from the target and the element itself onto the heap.

import heapqdef k_closest(nums, k, target):heap = []for num in nums:distance = abs(num - target)if len(heap) < k:heapq.heappush(heap, (-distance, num))elif distance < -heap[0][0]:heapq.heappushpop(heap, (-distance, num))distances = [pair[1] for pair in heap]distances.sort()return distances

At the end of the iteration, the heap will contain the k closest elements to the target. We can iterate over the heap and return the element associated with each tuple, and sort the result to return the elements in ascending order.

import heapqdef k_closest(nums, k, target):heap = []for num in nums:distance = abs(num - target)if len(heap) < k:heapq.heappush(heap, (-distance, num))elif distance < -heap[0][0]:heapq.heappushpop(heap, (-distance, num))distances = [pair[1] for pair in heap]distances.sort()return distances

### Solution

import heapqdef k_closest(nums, k, target):heap = []for num in nums:distance = abs(num - target)if len(heap) < k:heapq.heappush(heap, (-distance, num))elif distance < -heap[0][0]:heapq.heappushpop(heap, (-distance, num))distances = [pair[1] for pair in heap]distances.sort()return distances

### Complexity Analysis

**Time Complexity:** O(n * log k). We iterate over all the elements in the array. At each iteration, we comparing the current element with the root of the heap takes O(1) time. In the worst case, we both push and pop each element from the heap, which takes O(log k) time.

**Space Complexity:** O(k). The space used by the heap to store the k closest elements to the target.

### Bonus Approach: Two Pointers + Binary Search

We can also leverage the fact that the input array is sorted to solve this problem using a two-pointer approach combined with binary search. This approach is based on the observation that the k closest elements to the target will occur in a contiguous subarray of length k in the sorted array.

This algorithm initializes two pointers, left at index 0 and right at index len(nums) - k. The indicies between left and right represent the possible starting indices of the k closest elements to the target.

def findClosestElements(nums, k, target);left, right = 0, len(nums) - kwhile left < right:mid = left + (right - left) // 2if target - nums[mid] > nums[mid + k] - target:left = mid + 1else:right = midreturn nums[left:left + k]

We then perform a binary search to find the starting index of the k closest elements. We do so by first calculating the midpoint of the two pointers, mid. We then compare the distance of the element at mid from the target with the distance of the element at mid + k from the target.

In the case below, nums[mid] = 1 and nums[mid + k] = 8. Since target = 5, nums[mid + k] is closer to the target than nums[mid].

def findClosestElements(nums, k, target);left, right = 0, len(nums) - kwhile left < right:mid = left + (right - left) // 2if target - nums[mid] > nums[mid + k] - target:left = mid + 1else:right = midreturn nums[left:left + k]

This is where the choice to compare the distances between nums[mid] and nums[mid + k] comes into play. The contiguous subarray containing the k closest elements **cannot contain both nums[mid] and nums[mid + k]** because they are located k + 1 elements apart. So when nums[mid + k] is closer to the target than nums[mid], this tells us that the final answer *might include* nums[mid + k], but it will definitely not incldue nums[mid]. To reflect this, we move the left pointer to mid + 1 to indicate that the starting index of the k closest elements will be to the right of mid.

def findClosestElements(nums, k, target);left, right = 0, len(nums) - kwhile left < right:mid = left + (right - left) // 2if target - nums[mid] > nums[mid + k] - target:left = mid + 1else:right = midreturn nums[left:left + k]

(The opposite is true when nums[mid] is closer to the target than nums[mid + k]. In this case, the final answer *might include* nums[mid] but it will definitely not include nums[mid + k]. To reflect this, we move the right pointer to mid.)

Now, we've reduced the search space of the starting index of the k closest elements by half of its previous size. We can continue this process until left and right meet at the same index. At this point, the starting index of the k closest elements will be left.

def findClosestElements(nums, k, target);left, right = 0, len(nums) - kwhile left < right:mid = left + (right - left) // 2if target - nums[mid] > nums[mid + k] - target:left = mid + 1else:right = midreturn nums[left:left + k]

## Solution

def findClosestElements(nums, k, target);left, right = 0, len(nums) - kwhile left < right:mid = left + (right - left) // 2if target - nums[mid] > nums[mid + k] - target:left = mid + 1else:right = midreturn nums[left:left + k]

### Complexity Analysis

**Time Complexity:** O(log(n - k) + k). We run binary search over n - k elements, and in each iteration of the binary search, we reduce the search space by half, for a total run time of O(log(n - k)). After finding the starting index of the k closest elements, we iterate over the k closest elements to build the result array, which takes O(k) time.

**Space Complexity:** O(1). We only use a constant amount of extra space.

Loading comments...