二分查找_LeetCode找到K个最接近的元素
给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。如果有两个数与 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-k-closest-elements
解法1:遍历排序
利用sort函数,加上lambda表达式可以方便的按照每个元素与目标x的差值排序。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
sort(arr.begin(),arr.end(),[=](int v1,int v2){
if(abs(v1 - x) < abs(v2 - x))
return true;
else if(abs(v1 - x) == abs(v2 - x))
{
//如果差值相等,那么数字小的排前面
if(v1 < v2) return true;
else return false;
}
else
return false;
});
//按照题目要求返回升序序列
vector<int> vec(arr.begin(),arr.begin()+k);
sort(vec.begin(),vec.end());
return vec;
}
};
时间复杂度O(n)
解法二:二分查找+滑动窗口
这个方法非常的巧妙,同时也不好理解,先看下面这个图。
这种解法就是利用滑动窗口的思想,最终目的就是找到一个最满足题意的窗口,而且本身数组就是升序的,所以就可以把寻找范围的问题转化为寻找最终窗口的左边界的问题。这就是想到二分查找的原因,二分查找的关键在于找到合适的条件来二分区间,缩小搜索范围。这里的左边界初始搜索区间为[0, arr.size()-k];
这里的关键代码if(x - arr[mid] > arr[mid + k] - x)
,当前窗口为[mid, mid+k-1],右边的窗口为[mid+1, mid+k],因为这两个窗口的其他部分都是重合的,所以这里就用当前窗口的左边界和X的差值与右边窗口的右边界与X的差值对比。如果结果是大于,那么说明右边窗口更靠近X(右边窗口中每个元素与x的差值的绝对值之和更小),那么把当前窗口向右移动,left=mid+1;
最后left=right,否则向左移动(包括差值相等的情况),然后循环搜索。就找到了最终窗口的左边界。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int left = 0;
int right = arr.size() - k;
while(left < right)
{
int mid = (left + right) / 2;
if(x - arr[mid] > arr[mid + k] - x)
{
left = mid + 1;
}
else
{
right = mid;
}
}
return vector<int>(arr.begin() + left, arr.begin() + k + left);
}
};
时间复杂度O(logn+k)