LC658.找到K个最接近的元素
思路
- 二分查找 + 双指针
- 先利用二分查找,找到
>= x
的第一个值(>= x
区间的左端点),记为pos
。但是pos
位置的值,未必是离x
最近的值。可以在排除特殊情况pos == 0 || pos == len - 1
之后,比较arr[pos - 1]
和arr[pos]
离x
哪个更近,更近的那个更新为pos
位置。如果一样近,就选择左边的那个更新成pos
位置。 左指针
lp
,右指针rp
,起始时刻都指向离x
最近的位置pos
。然后左右移动两个指针,使得区间长度恰好为k
,然后返回数组切片arr[lp:rp]
即可。这一点看似简单,其实细节极其繁琐,不好弄。需要关注的细节如下。- 特殊情况:如果左指针到达边界
lp == 0
,那么直接动右指针即可;如果右指针到达边界rp == len - 1
,那么直接移动左指针即可。 - 一般情况下的移动规则:左右两根指针的下一个位置
arr[lp - 1]
和arr[rp + 1]
,哪一个离x
近,哪一个就往边缘动一格。 最后的终止条件,
lp
和rp
位置都是需要取进去的,所以长度rp - lp + 1 == k
,就说明满足长度条件,从而退出循环。代码
class Solution {
public:
int findPos(vector<int>& arr, int x) {
int left = 0, right = arr.size() - 1;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] >= x) {
right = mid;
} else {
left = mid + 1;
}
}
if (arr[left] >= x) {
return left;
} else {
return -1;
}
}
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
// 找到大于等于 x 的第一个数的下标
int n = arr.size();
int pos = findPos(arr, x);
// cout << pos << endl;
if (pos == -1) {
return {arr.end() - k, arr.end()};
} else if (pos == 0) {
return {arr.begin(), arr.begin() + k};
}
// 确保 pos 是离得最近的点
// 如果距离一致,优先选左边
pos = (abs(arr[pos] - x) < abs(arr[pos - 1] - x) ? pos : pos - 1);
int lp = pos, rp = pos;
while (rp - lp + 1 < k) {
if (lp == 0) {
rp += 1;
continue;
} else if (rp == n - 1) {
lp -= 1;
continue;
}
if (abs(x - arr[lp - 1]) <= abs(arr[rp + 1] - x)) {
lp -= 1;
} else {
rp += 1;
}
}
// [lp, rp]包头包尾
return {arr.begin() + lp, arr.begin() + rp + 1};
}
};
- 特殊情况:如果左指针到达边界