[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 }

 

相关题目

34. Find First and Last Position of Element in Sorted Array

658. Find K Closest Elements

LeetCode 题目总结

posted @ 2020-06-16 00:36  CNoodle  阅读(261)  评论(0编辑  收藏  举报