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};}};
- 特殊情况:如果左指针到达边界
