算法练习第45题-找到 K 个最接近的元素

243 阅读1分钟

一、题目

给定一个 排序好 的数组 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]

作者:力扣 (LeetCode) 链接:leetcode.cn/leetbook/re… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二、思路

双指针解法

  • 根据题意,算数组中与x值绝对距离最近的k个值
  • 使用双指针,首先定义长度len,右指针left,左指针right
  • while的判断条件是k < right - left
  • 左右指针通过距离的判断向中间移动,直到条件不成立
  • 由于是有序数组,
  • 所以左边值得距离为x - arr[left],右边值得距离为arr[right-1] - x
  • 因此当左指针的距离小于右指针的距离是 右指针--
  • 否则左指针++
  • 最后通过slice(left, right)返回k个数

双指针+二分查找

  • 双指针思路还是定义左右两个指针,但是这循环不再是循环整个数据
  • 循环的方式使用二分查找
  • 首先先定义len left right 定义中间值mid
  • 由于要找到k个数,因此这里的对比是x-arr[mid] 与arr[mid+k]-x
  • 对比双方,如果x-arr[mid] 大于arr[mid+k]-x时,说明右区间小,左指针等于mid+1
  • 否则就定义了右指针的区间
  • 这里有个问题,是这里的if判断为啥必须用x - arr[mid] > (arr[mid + k] - x)
  • 如果有兴趣可以输出下这个值,这里有个问题是mid + k的是有时候会大于len,因此arr[mid + k]的值是undefined,减x后为NaN
  • NaN与任意值对比都为false,这是如果使用x - arr[mid] < (arr[mid + k] - x)判断,那么就直接走入left = mid + 1,
  • 在len不足的情况下,循环就直接结束了, 因此这里只能先判断大于,不能判断小于
  • 之后循环在left < right条件不满足时结束,这里left与left+k这个区间就是我们想要的值
  • 返回arr.slice(left, left+k)

三、代码

双指针解法

let findClosestElements = function(arr, k, x) {
  /**
   * 根据题意,算数组中与x值绝对距离最近的k个值
   * 使用双指针,首先定义长度len,右指针left,左指针right
   * while的判断条件是k < right - left
   * 左右指针通过距离的判断向中间移动,直到条件不成立
   * 由于是有序数组,
   * 所以左边值得距离为x - arr[left],右边值得距离为arr[right-1] - x
   * 因此当左指针的距离小于右指针的距离是 右指针--
   * 否则左指针++
   * 最后通过slice(left, right)返回k个数
   * */ 
  let len = arr.length, left = 0, right = len 
  while (k < right - left) {
    const rightNum = arr[right-1] - x, leftNum = x - arr[left] 
    if (rightNum < leftNum) {
      left++
    } else {
      right--
    }
  }
  return arr.slice(left, right)
}
findClosestElements(arr, k, x)

双指针+二分查找

let findClosestElements = function(arr, k, x) {
  /**
   * 双指针+二分查找
   * 双指针思路还是定义左右两个指针,但是这循环不再是循环整个数据
   * 循环的方式使用二分查找
   * 首先先定义len left right 定义中间值mid
   * 由于要找到k个数,因此这里的对比是x-arr[mid] 与arr[mid+k]-x
   * 对比双方,如果x-arr[mid] 大于arr[mid+k]-x时,说明右区间小,左指针等于mid+1
   * 否则就定义了右指针的区间
   * 
   * 这里有个问题,是这里的if判断为啥必须用x - arr[mid] > (arr[mid + k] - x)
   * 如果有兴趣可以输出下这个值,这里有个问题是mid + k的是有时候会大于len,因此arr[mid + k]的值是undefined,减x后为NaN
   * NaN与任意值对比都为false,这是如果使用x - arr[mid] < (arr[mid + k] - x)判断,那么就直接走入left = mid + 1,
   * 在len不足的情况下,循环就直接结束了, 因此这里只能先判断大于,不能判断小于
   * 
   * 之后循环在left < right条件不满足时结束,这里left与left+k这个区间就是我们想要的值
   * 返回arr.slice(left, left+k)
   * */ 

  let len = arr.length, left = 0, right = len 
  while (left < right) {
    const mid = left + Math.floor((right - left -1)>>1)
    if(x - arr[mid] > (arr[mid + k] - x)) {
      left = mid + 1
    } else {
      right = mid
    }
  }
  return arr.slice(left, left+k)
}
findClosestElements(arr, k, x)

四、测试结果

双指针解法

image.png

双指针+二分查找

image.png