【二分查找】leetcode 658. 找到 K 个最接近的元素

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]

提示

  • 1 < = k < = a r r . l e n g t h 1 <= k <= arr.length 1<=k<=arr.length
  • 1 < = a r r . l e n g t h < = 1 0 4 1 <= arr.length <= 10^4 1<=arr.length<=104
  • a r r 按升序排列 arr 按 升序 排列 arr按升序排列
  • − 1 0 4 < = a r r [ i ] , x < = 1 0 4 -10^4 <= arr[i], x <= 10^4 104<=arr[i],x<=104

方法一:二分查找 + 双指针 + 排序

解题思路

首先利用二分查找在数组 arr 中找到 <= x 的最大的 arr[i],然后使用双指针的方法来找到 k 个最接近 x 的元素,最后对答案数组进行排序。

如何定位这 k 个最接近 x 的元素 ?
左指针为二分查找的结果 l l l,右指针为 l + 1 l + 1 l+1,然后根据题目要求判断左指针和右指针上的元素是否满足条件:

  • 如果 x − a r r [ l e f t ] < = a r r [ r i g h t ] − x x - arr[left] <= arr[right] - x xarr[left]<=arr[right]x,左指针上的元素存入数组,并向左移动;
  • 如果 x − a r r [ l e f t ] > a r r [ r i g h t ] − x x - arr[left] > arr[right] - x xarr[left]>arr[right]x,右指针上的元素存入数组,并向右移动;
  • 如果右指针已经移动到超过数组 arr 的长度,则左指针上的元素存入数组,并向左移动;
  • 如果左指针已经移动到小于 0 时,则右指针上的元素存入数组,并向右移动。

直到找到这 k 个最接近 x 的元素为止。

最后对答案数组升序排序,并返回。

代码

class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        vector<int> ans;
        int n = arr.size();
        int l = 0, r = n - 1, mid;
        while(l < r) 
        {
            mid = (l + r + 1) >> 1;
            if(arr[mid] <= x)
                l = mid;
            else
                r = mid - 1;
        }
        int left = l, right = l + 1;
        while(k--)
        {
            if(left >= 0 && right < n && x - arr[left] <= arr[right] - x)        
                ans.push_back(arr[left--]);
            else if(left >= 0 && right < n && x - arr[left] > arr[right] - x)
                ans.push_back(arr[right++]);
            else if(left >= 0 && right >= n)
                ans.push_back(arr[left--]);
            else if(left < 0 && right < n)
                ans.push_back(arr[right++]);
        }
        sort(ans.begin(), ans.end());
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)。二分查找的时间复杂度为 O ( l o g n ) O(logn) O(logn),双指针遍历数组的时间复杂度为 O ( n ) O(n) O(n),排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),因此整体时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( k ) O(k) O(k)。答案数组所需的空间开销。

方法二:二分查找最优区间的左边界

解题思路

我们利用二分查找找到最优区间的左边界。
那二分查找的左端点为 0,右端点为 a r r . s i z e ( ) − k arr.size() - k arr.size()k

判断区间 [ m i d , m i d + k ] [mid,mid + k] [mid,mid+k] 是否符合条件:

  • 如果 x 不在此区间内,并且 < a r r [ m i d ] < arr[mid] <arr[mid],则右端点最多为 mid;
  • 如果 x 不在此区间内,并且 > a r r [ m i d + k ] > arr[mid + k] >arr[mid+k],则左端点为 mid + 1;
  • 如果 x 靠近 a r r [ m i d ] arr[mid] arr[mid],则右端点最多为 mid;
  • 如果 x 靠近 a r r [ m i d + k ] arr[mid + k] arr[mid+k],则左端点为 mid + 1。

直到 l = r l = r l=r 结束循环,找到最优区间的左边界。

代码

class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        vector<int> ans;
        int l = 0, r = arr.size() - k, mid;
        while(l < r)
        {
            mid = (l + r) >> 1;
            if(x - arr[mid] > arr[mid + k] - x)
                l = mid + 1;
            else 
                r = mid;
        }
        for(int i = 0; i < k; i++)
            ans.push_back(arr[i + l]);
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O ( l o g n ) O(logn) O(logn)。二分查找的时间复杂度为 O ( l o g n ) O(logn) O(logn)
  • 空间复杂度: O ( k ) O(k) O(k)。答案数组所需的空间开销。

你可能感兴趣的:(#,双指针,#,二分查找,算法之路,leetcode,算法,数据结构)