前端算法第一二九弹-找到 K 个最接近的元素

150 阅读2分钟

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战

给定一个 排序好 的数组 arr ,两个整数 kx ,从数组中找到最靠近 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]

算法

直观地,我们可以将数组中的元素按照与目标 x 的差的绝对值排序,排好序后前 k 个元素就是我们需要的答案。

var findClosestElements = function (arr, k, x) {
  arr.sort((a, b) => (Math.abs(a - x) - Math.abs(b - x) == 0 ? a - b : Math.abs(a - x) - Math.abs(b - x)))
  console.log(arr);
  return arr.splice(0, k).sort((a, b) => a - b)

};

二叉查找和双指针

原本的数组是有序的,所以我们可以像如下步骤利用这一特点。

  1. 如果目标 x 小于等于有序数组的第一个元素,那么前 k 个元素就是答案。

  2. 类似的,如果目标 x 大于等于有序数组的最后一个元素,那么最后 k 个元素就是答案。

  3. 其他情况,我们可以使用二分查找来找到恰好大于 x 一点点的元素的索引 index 。然后让 low 等于 index 左边 k-1 个位置的索引,high 等于 index 右边 k-1 个位置的索引。我们需要的 kkk 个数字肯定在范围 [index-k-1, index+k-1] 里面。所以我们可以根据以下规则缩小范围以得到答案。

    • 如果 low 小于 0 或者 low 对应的元素比 high 对应的元素更接近 x ,那么减小 high 索引。
    • 如果 high 大于最后一个元素的索引 arr.size()-1 或者它比起 low 对应的元素更接近 x,那么增加 low 索引。
    • 当且仅当 [low, high] 之间恰好有 k 个元素,循环终止,此时范围内的数就是答案。
    var findClosestElements = function (arr, k, x) {
      let low = 0,
        high = arr.length - 1;
      while (low < high) {
        const mid = low + Math.floor((high - low) / 2);
        x - arr[mid] > arr[mid + k] - x ? low = mid + 1 : high = mid;
      }
      return arr.slice(low, low + k);
    };