news 2026/10/9 7:27:11

【灵神高频面试题合集04-05】二分查找

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【灵神高频面试题合集04-05】二分查找

基础算法精讲·题目汇总:灵茶山艾府 - 【基础算法精讲】- GitHub

视频:灵茶山艾府的个人空间-灵茶山艾府个人主页-哔哩哔哩视频


二分查找

【题单】二分:https://leetcode.cn/circle/discuss/SqopEo/

04 二分查找 红蓝染色法

课程讲解

原始二分查找(模板代码)

要求 nums 是非递减的,即 nums[i] <= nums[i+1],返回最小的满足 nums[i] >= target 的 i。如果不存在,返回 len(nums)

二分查找在闭区间上的写法,以及对比开区间、半闭半开区间上的写法

≥、>target(的第一个数)、≤、<target(的最后一个数)写法的差别

  • >x 等价于 ≥ x+1(的第一个数)
  • <x 可以看成 ≥ x 的第一个数,它左边的那个数
  • ≤x 可以看成 > x 的第一个数,它左边的那个数

红色更新 left 指针,蓝色更新 right 指针

# 左闭右闭 def search(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) - 1 # [left, right] while left <= right: # 区间不为空 mid = (left + right) // 2 # 或写成 left + (right - left) // 2 if nums[mid] < target: left = mid + 1 # [mid+1, right] else: right = mid - 1 # [left, mid-1] return left # 左闭右开 def search(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) # [left, right) while left < right: # 区间不为空 mid = (left + right) // 2 # 或写成 left + (right - left) // 2 if nums[mid] < target: left = mid + 1 # [mid+1, right) else: right = mid # [left, mid) return left # 或 return right 均可 # 左开右开 def search(self, nums: List[int], target: int) -> int: left = -1 right = len(nums) # (left, right) while left+1 < right: # 区间不为空 mid = (left + right) // 2 # 或写成 left + (right - left) // 2 if nums[mid] < target: left = mid # (mid, right) else: right = mid # (left, mid) return right
  • 由于每次都去掉了一半的元素,时间复杂度为 O(logn)
  • 空间复杂度 O(1),没有用到额外空间
34. 在排序数组中查找元素的第一个和最后一个位置

等于求 target 的开始位置和结束位置,即分别是 ≥ 和 ≤

class Solution: # 左闭右闭版本的模板代码 def search(self, nums, target): left = 0 right = len(nums) - 1 # [left, right] while left <= right: # 区间不为空 mid = (left + right) // 2 # 或写成 left + (right - left) // 2 if nums[mid] < target: left = mid + 1 # [mid+1, right] else: right = mid - 1 # [left, mid-1] return left def searchRange(self, nums: List[int], target: int) -> List[int]: start = self.search(nums, target) # ≥ target的第一个位置 # 如果所有数都 < target 或 这个数不等于target if start == len(nums) or nums[start] != target: return [-1, -1] # ≤ target的最后一个位置,可以转化成 # > target的第一个数,它左边的那个数 # > target等价于 ≥ target + 1 end = self.search(nums, target+1) - 1 # -1表示它左边的那个数 return [start, end]
  • 时间O(logn),空间O(1)

课后作业

275. H 指数 II
  • 在索引 i 的右侧(包括 i 本身)一共有 n-i 篇论文
  • 如果这 n-i 篇论文的引用次数都至少为 citations[i],即 citations[i] >= n-i(就是一个有效的h指数候选值)

H 指数的定义是:至少有h篇论文,每篇被引用了至少h次。
现在有n-i篇论文。我们令h = n-i。
我们想让这n-i篇论文全都满足“被引用至少n-i次”。
那怎么判断它们全都满足呢?只要这堆论文里最差的那个(也就是 citations[i])满足就行了
所以,只要citations[i] >= n-i成立,就说明这 n-i 篇论文的引用次数全都 >= n-i

  • 求最大的h指数,等价于求最小(最左边)的 i
class Solution: def hIndex(self, citations: list[int]) -> int: # citations[n-h] >= h n = len(citations) left, right = 0, n-1 ans = 0 while left <= right: mid = (left+right) // 2 # n-mid 表示从 mid 到末尾的论文数量 if citations[mid] >= n-mid: ans = n-mid # 满足条件,记录答案 right = mid-1 # 尝试在左半部分寻找更大的 h else: # 引用次数不够,需要在右半部分寻找更大的 h left = mid+1 return ans
暂时未做

2529. 正整数和负整数的最大计数

2300. 咒语和药水的成功对数

1385. 两个数组间的距离值

2080. 区间内查询数字的频率

2563. 统计公平数对的数目

875. 爱吃香蕉的珂珂

2187. 完成旅途的最少时间

275. H 指数 II(已做)

2861. 最大合金数

2439. 最小化数组中的最大值

2517. 礼盒的最大甜蜜度


05 数组峰值 搜索旋转排序数组

课程讲解

162. 寻找峰值

找到一个峰顶,大于左右两侧相邻的元素

  • 比如下图中的2(第一个2)、4、6(因为可以假设nums[-1] = nums[n] = -∞)都是峰顶
  • 由于峰顶一定在数组中,所以数组最右侧的元素一定是蓝色的(n-1要么是峰顶,要么在峰顶右侧)
  • 因此二分时,可以初始化 left=0,right=n-2(n-1一定是蓝色,无需再二分)
  • 可以通过比较 M 和 M+1 指向的数字来染色。题目保证了这两个数字一定不相等(对于所有有效的i都有nums[i] != nums[i + 1]),所以要么小于,要么大于
  • 若是小于,说明 M 在峰顶左侧(M右侧存在峰顶),都是红色,更新left
  • 若是大于,说明 M 要么是峰顶,要么在峰顶右侧(M左侧存在峰顶),都是蓝色,更新right
  • 二分循环结束后,L就是答案(左闭右闭写法时)

# 左闭右闭写法 class Solution: def findPeakElement(self, nums: List[int]) -> int: # [0, n-2] left, right = 0, len(nums)-2 while left <= right: mid = (left + right) // 2 if nums[mid] < nums[mid+1]: left = mid + 1 else: right = mid - 1 return left # 左开右开写法 class Solution: def findPeakElement(self, nums: List[int]) -> int: # [0, n-2] # (-1, n-1) left, right = -1, len(nums)-1 while left + 1 < right: mid = (left + right) // 2 if nums[mid] < nums[mid+1]: # 红色 left = mid else: # 蓝色 right = mid return right
  • 时间O(logn),空间O(1)
153. 寻找旋转排序数组中的最小值

给你一个数组,它可能是一个递增的数组,也有可能是两段递增数组且第一个数 > 最后一个数。如何用O(logn) 的时间找到数组的最小值?

  • 需要一个判定方式来判断 nums[mid](即二分的位置)是在最小值的左侧还是右侧
  • 可以和最后一个数比大小。由于最小值一定在数组中,那么最后一个数要么是最小值,要么在最小值的右侧。因此 n-1 一定是蓝色
  • 因此,在 0 ~ n-2 中二分
    • 如果 nums[mid] < 最后一个数,那么 nums[mid] 所处的位置有两种情况:在一段递增数组中,或者在两段递增数组中的第二段。无论是哪种情况,nums[mid] 要么是最小值,要么在最小值右侧。染成蓝色
    • 如果 nums[mid] > 最后一个数,那么 nums[mid] 只可能在两段递增数组中,且一定在最小值左侧(第一段)。染成红色

class Solution: def findMin(self, nums: List[int]) -> int: # [0, n-2] # (-1, n-1) left, right = -1, len(nums)-1 while left + 1 < right: mid = (left + right) // 2 if nums[mid] > nums[-1]: # 红色 left = mid else: # 蓝色 right = mid return nums[right]
33. 搜索旋转排序数组

【力扣-Python-33】搜索旋转排序数组(middle)

找 target(可能不在数组中),需要在 [0, n-1] 上二分,有两种做法:

  • 参考153题,首先找到最小值,然后比较 target 和最后一个数的大小,来判断在哪段二分查找 target。需要两次二分
  • 可以只一次二分。分三种情况讨论,什么时候nums[mid] 在 target 及其右侧(染成蓝色)
    • 如果二分的位置 > 最后一个数,说明在第一段。如果此时 target 也大于最后一个数,说明 target 也在第一段。且如果 nums[mid] >= target,说明在 target 及其右侧,染成蓝色
    • 如果二分的位置 ≤ 最后一个数,说明在第二段。如果此时 target 大于最后一个数,说明 target 在第一段。直接就说明 nums[mid] 在 target 及其右侧(染成蓝色)
    • 如果二分的位置 ≤ 最后一个数,说明在第二段。target 也在第二段,nums[mid] >= target,这种情况也是蓝色
  • 其余情况就是红色
class Solution: def is_blue(self, nums, i, target): end = nums[-1] if nums[i] > end: return target > end and nums[i] >= target else: return target > end or nums[i] >= target # [0, n-1] # (-1, n) def search(self, nums: List[int], target: int) -> int: left, right = -1, len(nums) while left + 1 < right: mid = (left + right) // 2 if self.is_blue(nums, mid, target): right = mid else: left = mid if right == len(nums) or nums[right] != target: return -1 return right

课后作业

74. 搜索二维矩阵

【力扣-Python-74】搜索二维矩阵(middle)

(整体二分)整个矩阵行内有序,行间也有序,可以把这个二维矩阵想象成一个一维的有序数组

  • 定义一个映射关系,对于一维索引,用整除列数得到行号,用取余列数得到列号
  • 即:一维索引 index —> 二维行号 = index // n,二维列号 = index % n
  • 有了这个映射,就可以直接对整个矩阵进行一次二分查找
class Solution: def searchMatrix(self, matrix: list[list[int]], target: int) -> bool: m, n = len(matrix), len(matrix[0]) left, right = 0, m*n-1 while left <= right: mid = (left+right) // 2 row, col = mid // n, mid % n if matrix[row][col] == target: return True elif matrix[row][col] < target: left = mid + 1 else: right = mid - 1 return False
暂时未做

1901. 寻找峰值 II

154. 寻找旋转排序数组中的最小值 II

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 7:26:07

Codex桌面版更新后无法加载组织设置的排查与修复全记录

那天上午我像往常一样打开 Codex 桌面版&#xff0c;右下角弹出了新版本更新提示。想着这类工具更新无非是修几个小问题、加一些模型选项&#xff0c;我随手点了「下载并重启」。结果这一更新&#xff0c;事情就不对劲了。重启后应用没有进入熟悉的工作区&#xff0c;而是卡在启…

作者头像 李华
网站建设 2026/10/9 7:26:03

GitHub周榜项目筛选与评估:从榜单到技术雷达的实战方法

1. 周榜项目的价值不在"榜单"本身&#xff0c;而在筛选逻辑每周都有大量的人在各个渠道转发各种热榜截图&#xff0c;但真正把榜单用起来的人少之又少。大部分人看到榜单的第一反应是"收藏了等于学会了"&#xff0c;然后就没有然后了。我做项目评估这些年&…

作者头像 李华
网站建设 2026/10/9 7:25:37

MyBatis日期查询边界陷阱:从隐式转换到左闭右开区间

1. “查询一整天”这个需求&#xff0c;为什么在 MyBatis 里总翻车1.1 一个五分钟需求背后的数据类型错位先说个我闭着眼都能背出来的场景&#xff1a;运营后台有个订单列表&#xff0c;产品提的需求是“按日期筛选&#xff0c;选 12 月 1 日就出 12 月 1 日这一天的所有订单”…

作者头像 李华
网站建设 2026/10/9 7:21:42

仓库管理六大核心KPI:从数据指标到实时风控的落地指南

1. 为什么这6个KPI是仓库管理的“命脉”&#xff0c;而不是可有可无的数字&#xff1f;干了十多年仓储物流系统咨询和现场优化&#xff0c;我经手过从200平米社区前置仓到30万平米智能分拨中心的各类项目。见过太多仓库主管把日报表当摆设&#xff0c;也见过不少企业花几百万上…

作者头像 李华