有序数组,距离目标最近的k个数 二分查找

发布于:2025-09-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

  🤔 新手做题思路:

  第1步:理解题目

  - 找距离x最近的k个数
  - 数组已排序
  - 返回结果也要排序(升序)
  - 距离相同时,选择较小的数

  第2步:关键insight

  - 数组已排序 → 考虑二分查找
  - 最近的k个数一定是连续的k个数 → 考虑滑动窗口/双指针
  - 只需要找到这个连续区间的起始位置

数组排序和二分查找有关 ,注意滑动窗口不用排序

指定的子串长度和滑动窗口有关

  第3步:核心思想

  最近的k个数字一定形成一个长度为k的连续子数组,问题转化为:
  在所有长度为k的子数组中,找到距离x总和最小的那个

● 💡 解题方法(从简单到优化):

  方法1:暴力法(新手先理解这个)

  def findClosestElements_brute(arr, k, x):
      # 计算每个元素与x的距离,然后排序
      distances = []
      for num in arr:
          distances.append((abs(num - x), num))

      # 按距离排序,距离相同时按值排序
      distances.sort(key=lambda item: (item[0], item[1]))

      # 取前k个元素
      result = [item[1] for item in distances[:k]]

      # 返回排序结果
      return sorted(result)

  方法2:双指针法(推荐理解)

  def findClosestElements_two_pointers(arr, k, x):
      left, right = 0, len(arr) - 1

      # 收缩到k个元素
      while right - left >= k:
          # 比较左右边界哪个离x更远,移除更远的
          if abs(arr[left] - x) > abs(arr[right] - x):
              left += 1
          elif abs(arr[left] - x) < abs(arr[right] - x):
              right -= 1
          else:
              # 距离相同,移除更大的数
              if arr[left] > arr[right]:
                  left += 1
              else:
                  right -= 1

      return arr[left:right + 1]

  方法3:二分查找(最优解)

  def findClosestElements_binary(arr, k, x):
      # 二分查找最佳起始位置
      left, right = 0, len(arr) - k

      while left < right:
          mid = (left + right) // 2

          # 比较mid位置和mid+k位置哪个更适合作为起点
          # 如果x离右边界更近,说明最佳区间应该右移
          if x - arr[mid] > arr[mid + k] - x:
              left = mid + 1
          else:
              right = mid

      return arr[left:left + k]

  🔍 详细解析双指针法(最容易理解):

  核心思想:

  - 初始时:左指针=0,右指针=n-1(包含所有元素)
  - 目标:缩小到恰好k个元素
  - 策略:每次移除距离x最远的边界元素

  手动模拟:

  arr = [1,2,3,4,5], k = 4, x = 3

  初始: left=0, right=4, 区间=[1,2,3,4,5] (5个元素)
  比较: |1-3|=2 vs |5-3|=2,距离相等,移除较大的5
  结果: left=0, right=3, 区间=[1,2,3,4] (4个元素) ✓

  完整代码实现:

● def findClosestElements(arr, k, x):
      """
      推荐的双指针解法
      时间复杂度: O(n)
      空间复杂度: O(1)
      """
      left, right = 0, len(arr) - 1

      # 收缩窗口直到剩下k个元素
      while right - left + 1 > k:
          # 计算左右边界与x的距离
          left_dist = abs(arr[left] - x)
          right_dist = abs(arr[right] - x)

          if left_dist > right_dist:
              # 左边更远,移除左边
              left += 1
          elif left_dist < right_dist:
              # 右边更远,移除右边
              right -= 1
          else:
              # 距离相等,移除数值更大的(题目要求)
              if arr[left] > arr[right]:
                  left += 1
              else:
                  right -= 1

      return arr[left:right + 1]

  # 测试
  print(findClosestElements([1,2,3,4,5], 4, 3))      # [1,2,3,4]
  print(findClosestElements([1,1,2,3,4,5], 4, -1))  # [1,1,2,3]

  🚀 二分查找优化版(面试加分):

  def findClosestElements_optimal(arr, k, x):
      """
      二分查找最优起始位置
      时间复杂度: O(log n)
      空间复杂度: O(1)
      """
      # 在[0, n-k]范围内找最佳起始位置
      left, right = 0, len(arr) - k

      while left < right:
          mid = (left + right) // 2

          # 关键判断:比较mid和mid+k位置
          # 如果x更靠近右边界,说明最优区间应该右移
          if x - arr[mid] > arr[mid + k] - x:
              left = mid + 1
          else:
              right = mid

      return arr[left:left + k]

  🎯 复杂度对比:

  | 方法   | 时间复杂度      | 空间复杂度 | 难度  |
  |------|------------|-------|-----|
  | 暴力法  | O(n log n) | O(n)  | ⭐   |
  | 双指针  | O(n)       | O(1)  | ⭐⭐  |
  | 二分查找 | O(log n)   | O(1)  | ⭐⭐⭐ |

  📝 考试建议:

  优先掌握双指针法:
  - 思路清晰,容易理解
  - 代码简洁,不容易出错
  - 时间复杂度已经很好了

  二分查找法作为加分项:
  - 面试时展现算法功底
  - 但逻辑相对复杂,容易出错

  ★ Insight ─────────────────────────────────────
  这道题的关键洞察是:最近的k个元素必然是连续的。因为如果不连续,中间跳过的元素肯定比端点的某个元素更接近x,这与最优性矛盾。基于这个洞察,问题就转化为寻找最
  优的连续k元素子数组,可以用双指针或二分查找来高效求解。
  ─────────────────────────────────────────────────


网站公告

今日签到

点亮在社区的每一天
去签到