有序数组,距离目标最近的k个数 二分查找
🤔 新手做题思路:
第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 += 1elif abs(arr[left] - x) < abs(arr[right] - x):right -= 1else:# 距离相同,移除更大的数if arr[left] > arr[right]:left += 1else:right -= 1return arr[left:right + 1]
方法3:二分查找(最优解)
def findClosestElements_binary(arr, k, x):# 二分查找最佳起始位置left, right = 0, len(arr) - kwhile left < right:mid = (left + right) // 2# 比较mid位置和mid+k位置哪个更适合作为起点# 如果x离右边界更近,说明最佳区间应该右移if x - arr[mid] > arr[mid + k] - x:left = mid + 1else:right = midreturn 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元素子数组,可以用双指针或二分查找来高效求解。
─────────────────────────────────────────────────