[LeetCode] 658. Find K Closest Elements
Given a sorted array arr
, two integers k
and x
, find the k
closest elements to x
in the array. The result should also be sorted in ascending order. If there is a tie, the smaller elements are always preferred.
Example 1:
Input: arr = [1,2,3,4,5], k = 4, x = 3 Output: [1,2,3,4]
Example 2:
Input: arr = [1,2,3,4,5], k = 4, x = -1 Output: [1,2,3,4]
Constraints:
1 <= k <= arr.length
1 <= arr.length <= 10^4
- Absolute value of elements in the array and
x
will not exceed 104
找到K个最接近的元素。
给定一个排序好的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-k-closest-elements
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
最优解是二分法。因为 input 数组是有序的而且找的数字一定是在数组中,所以可以用二分法。这个题虽然找的是一个区间(若干个数字),但是由于要找的数字的个数是确定的,所以如果找到了区间的左边界,也就等同于找到了整个区间。这一点很像34题。
关于这个要找的左边界,他的范围是[0, nums.length - K),注意是左闭右开的区间。我参考了这个帖子。为什么右边是开区间?举个例子,比如数组的长度就是 K,如果此时 X 大于数组的最后一个数字,说明要找的范围要整体往右移动,此时左边界需要移动到 arr.length - k 的位置上,否则会越界。
每当找到一个 mid 数字的时候,比较的是 nums[mid] 和 nums[mid + K] 谁与 X 的差值更小,以决定二分法到底是往左还是往右。这里我们判断的时候不需要,也不能添加绝对值符号。我这里分享一个写的很好的帖子说明为什么不需要加绝对值符号。
这里 X 和我们要找的区间的位置关系,有如下四种情况(引用)。可以这样考虑,x - A[mid] > A[mid + k] - x 其实就等价于 x > (A[mid + k] + A[mid]) / 2。等于是比较 X 和区间平均值的大小关系。
case 1: x - A[mid] < A[mid + k] - x, need to move window go left
-------x----A[mid]-----------------A[mid + k]----------
case 2: x - A[mid] < A[mid + k] - x, need to move window go left again
-------A[mid]----x-----------------A[mid + k]----------
case 3: x - A[mid] > A[mid + k] - x, need to move window go right
-------A[mid]------------------x---A[mid + k]----------
case 4: x - A[mid] > A[mid + k] - x, need to move window go right
-------A[mid]---------------------A[mid + k]----x------
时间O(logn)
空间O(k) - output array
Java实现
1 class Solution { 2 public List<Integer> findClosestElements(int[] arr, int k, int x) { 3 int left = 0; 4 int right = arr.length - k; 5 while (left < right) { 6 int mid = left + (right - left) / 2; 7 if (x - arr[mid] > arr[mid + k] - x) { 8 left = mid + 1; 9 } else { 10 right = mid; 11 } 12 } 13 List<Integer> res = new ArrayList<>(); 14 for (int i = left; i < left + k; i++) { 15 res.add(arr[i]); 16 } 17 return res; 18 } 19 }
这里我再补充一个很巧妙的双指针的做法。我参考了这个帖子。这个思路很好地利用了 input 数组是排序的条件,同时写起来也不是很难。
时间O(n) - worse case
空间O(k) - output
Java实现
1 class Solution { 2 public List<Integer> findClosestElements(int[] arr, int k, int x) { 3 int left = 0; 4 int right = arr.length - 1; 5 while (left + k <= right) { 6 if (Math.abs(arr[left] - x) > Math.abs(arr[right] - x)) { 7 left++; 8 } else { 9 right--; 10 } 11 } 12 List<Integer> res = new ArrayList<>(); 13 for (int i = left; i < left + k; i++) { 14 res.add(arr[i]); 15 } 16 return res; 17 } 18 }
相关题目