leetcode 658. 找到 K 个最接近的元素
题目描述:
给定一个排序好的数组 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]
解题分析:
方法一:双指针
以 arr = [1, 2, 3, 4, 5] , x = 3, k = 4 为例。
即删除法。由题意可得,最终需要最接近 3 的 4 个数,而因为是有序数组,且返回的是连续升序子数组,所以删除掉边界的那一个数即可;那么就可以使用双指针的方式来保留区间。
只需要比较 arr[0] 和 arr[4] 与 x 的差值大小即可,删除差值大的那一个,差值相同时保留数组中下标小的那一个,所以例题答案为 [1,2,3,4] 。
时间复杂度:O(n)。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int n = arr.size() - 1;
int l = 0, r = n;
k --;
while(r - l > k) {
if(abs(arr[l] - x) > abs(arr[r] - x))
//缩小区间,前指针右移,删除前边界的数
l ++;
else
//缩小区间,后指针左移,删除后边界的数
r --;
}
vector<int> res;
while(l <= r) res.push_back(arr[l ++]);
return res;
}
};
方法二:二分法
题目要求接近 x 的 k 个数,要求返回的是区间,并且是连续区间;
也就是说,只要我们找到了左边界的下标,然后从左边界开始数 k 个数,返回就好了。
因此,我们可以先给出左边界所在区间 [0, size - k],保证向后取 k 个数,然后不断缩小区间范围,直至得到左边界的下标,就可以满足题意。
我们可以从 [0, size - k] 这个区间的**中间位置(arr[mid])**开始,定位一个长度为 (k + 1) 的区间,根据他们与 x 的差值大小,来判断区间的合理性,从而不断缩小区间。
时间复杂度: O(logn+k)。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
//左边界区间取值为[0, size - k]
int l = 0, r = arr.size() - k;
while (l < r) {
//从左边界向后去取k个数
int mid = l + (r - l) / 2;
//使用二分法确定左边界的值
if (x - arr[mid] > arr[mid + k] - x) {
//区间左边过大,右移缩小
l = mid + 1;
}
//左移缩小
else {
r = mid;
}
}
vector<int> res;
for (int i = l; i < l + k; i++){
res.push_back(arr[i]);
}
return res;
}
};