题目

给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。

整数 a 比整数 b 更接近 x 需要满足:

|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b

示例 1:

输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]

示例 2:

输入:arr = [1,2,3,4,5], k = 4, x = -1
输出:[1,2,3,4]

提示:

1 <= k <= arr.length
1 <= arr.length <= 10^4
arr 按 升序 排列
-10^4 <= arr[i], x <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-k-closest-elements
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

一、最无脑的解法是优先队列,开辟一个大小为k的优先队列,遍历每一个数计算其和x的差值,看能否入队,最后将队列的k个元素排好序返回就行了。见代码一。

二、双指针的解法也比较好想,可以先用找到最接近x的那个数,然后从这个数往两边扩散找够k个最接近x的。

不过,还有不需要二分的双指针思路,直接使用两个指针从数组两端向中间靠拢,那边更接近x就移动哪边的指针。见代码二。

三、直接二分的思路不太好想,可以看liweiwei的「题解」,主要思路是二分结果数组的左边界,因为长度为k的相邻的两个子数组只有端点处不同,所以可以看做是在k+1的子数组两端选择删除一个元素,就等同于左边界的移动。见代码三。

代码

代码一 优先队列

  1. class Solution {
  2. public List<Integer> findClosestElements(int[] arr, int k, int x) {
  3. PriorityQueue<int[]> minHeap = new PriorityQueue<>(k, (a, b) -> b[1] - a[1]);
  4. for (int num : arr) {
  5. int diff = Math.abs(num - x);
  6. if (minHeap.size() < k) {
  7. minHeap.offer(new int[]{num, diff});
  8. } else if (diff < minHeap.peek()[1]) {
  9. minHeap.poll();
  10. minHeap.offer(new int[]{num, diff});
  11. }
  12. }
  13. List<Integer> ans = new ArrayList<>();
  14. while (!minHeap.isEmpty()) {
  15. ans.add(minHeap.poll()[0]);
  16. }
  17. Collections.sort(ans);
  18. return ans;
  19. }
  20. }

代码二 双指针

  1. class Solution {
  2. public List<Integer> findClosestElements(int[] arr, int k, int x) {
  3. List<Integer> ans = new ArrayList<>();
  4. int n = arr.length;
  5. int l = 0;
  6. int r = n - 1;
  7. int step = n - k;
  8. while (step > 0) {
  9. // l指向的数和x差值比r更大,移动l指针
  10. if (x - arr[l] > arr[r] - x) {
  11. l++;
  12. } else {
  13. // 否则移动r指针,注意等于时移动r,因为题目要求确保差值相同时数字要更小的
  14. r--;
  15. }
  16. step--;
  17. }
  18. for (int i = l; i <= r; i++) {
  19. ans.add(arr[i]);
  20. }
  21. return ans;
  22. }
  23. }

代码三 二分

  1. class Solution {
  2. public List<Integer> findClosestElements(int[] arr, int k, int x) {
  3. int n = arr.length;
  4. int low = 0;
  5. int high = n - k;
  6. while (low < high) {
  7. int mid = low + (high - low) / 2;
  8. if (x - arr[mid] > arr[mid + k] - x) {
  9. low = mid + 1;
  10. } else {
  11. // 这里同样等于时删除右边的元素,确保差值相同时数字更小
  12. high = mid;
  13. }
  14. }
  15. List<Integer> res = new ArrayList<>(k);
  16. for (int i = low; i < low + k; ++i) {
  17. res.add(arr[i]);
  18. }
  19. return res;
  20. }
  21. }