给定一个有序数组,两个整数k和x,找到数组中最接近x的元素。 结果也应按升序排序。 如果有平局,小元素总是首选。
例1:
输入:[1,2,3,4,5],k = 4,x = 3
输出:[1,2,3,4]
例2:
输入:[1,2,3,4,5],k = 4,x = -1
输出:[1,2,3,4]
注意:
值k是正数,并且总是小于排序数组的长度。
给定数组的长度是正数,不会超过104
数组和x中元素的绝对值不会超过104
思路1:通过二分查找的变种解法,先找到第一个大于等于x的数字,然后左右比较谁离x近,就依次返回对应的下标,一直左右延伸到res中有k个数字。
参考代码:
vector findClosestElements(vector& arr, int k, int x) {
vector res;
if (arr[0] >=x ) {
for (int i = 0; i < k; i++) {
res.push_back(arr[i]);
}
return res;
}
if (arr[arr.size()-1] <= x) {
for (int i = (arr.size() - k); i < arr.size(); i++) {
res.push_back(arr[i]);
}
return res;
}
int left = 0;
int right = arr.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] < x) {
left = mid + 1;
}
else {
right = mid;
}
}
int slow = left-1;
int fast = left;
while (res.size() != k && slow>=0 && fast abs(arr[fast] - x)) {
res.push_back(arr[fast]);
fast++;
}
else {
if(slow==fast){
res.push_back(arr[slow]);
slow--;
fast++;
}
else{
res.push_back(arr[slow]);
slow--;
}
}
}
if (res.size() == k) {
sort(res.begin(), res.end());
return res;
}
while (res.size() != k) {
if (slow >= 0) {
res.push_back(arr[slow]);
slow--;
}
else {
res.push_back(arr[fast]);
fast++;
}
}
sort(res.begin(), res.end());
return res;
}
思路二:也是二分法,可以直接返回需要的k个数字的最左的下标,首先初始化left=0,right=arr.size()-k,保证在二分过程中不会越界,每次比较arr[mid]和arr[mid+k]哪个离x近,如果arr[mid+k]近,那么意味着如下不变约束成立:
abs(arr[mid+k+d]-target)
即对以arr[mid+k]为起点向右数k个数肯定比以arr[mid]为起点向右数k个数更靠近target。所以以arr[mid]为起点及小于mid的部分肯定不是最优解,所以抛弃mid的左半部分(包含mid),即left=mid+1。同理对于另外一边也是相同的思想,最后的结论是right=mid。这里要注意死循环处理。即:
abs(arr[mid+k+d]-target)>=abs(arr[mid+d]-target)
这里一定要包含等于符号,否则对于如下的输入[1,2,3,4,5] k=4,target=3。最后会出现两个满足条件的结果[1,2,3,4]和[2,3,4,5]由于题目要求当距离相等时,优先考虑左边,所以等于符号的左右便是当出现距离的情况是,让right=mid,也罢右边界收缩到mid,从而保留左半部分。
参考代码如下:
vector findClosestElements(vector& arr, int k, int x) {
int left = 0;
int right = arr.size() - k;
while (left < right) {
int mid = left + (right - left) / 2;
if (abs(arr[mid] - x) <= abs(arr[mid + k] - x)) {
right = mid;
}
else {
left = mid+1;
}
}
return vector(arr.begin()+left, arr.begin() + left+k);
}