二分查找类算法题核心笔记(含个人核心疑惑标注)
一、核心公理(底层铁律,解决「left==right何时出现」的疑惑)
【核心疑惑1】:left==right到底什么时候出现?是不是只有区间缩到1个元素时才会出现?
left == right↔ 二分搜索的有效区间长度为1 (仅含1个元素),无任何例外:- 场景1:初始区间就1个元素(如nums=[5],直接left=right=0);
- 场景2:从多元素区间缩小到1个元素(如nums=[1,3,5,7]缩到left=right=3);
- 区间长度≠1时:
- 长度>1 →
left < right(绝对不会出现left==right); - 长度=0 →
left > right(区间为空,无元素可查);
- 长度>1 →
- 二分法的本质:通过「缩小有效区间」逼近答案,核心差异仅在于「是否处理长度为1的区间(即left==right的情况)」。
二、题型核心分类(解决「什么时候用left<right,什么时候用left<=right」的核心疑惑)
【核心疑惑2】:为什么找最小值只用left<right(到leftright就停),找插入位置却要用left<=right(还要处理leftright)?
| 分类 | 核心目标 | 对「left==right(长度1区间)」的处理 | 循环条件 | 选择逻辑(解答核心疑惑) | 最终答案逻辑 |
|---|---|---|---|---|---|
| 类型A:找边界/定值 | 找数组中「已存在的元素位置」(最小值/最大值/第一个≥target的元素) | 停止处理,直接取该位置为答案 | while left < right |
答案是数组里"本来就有"的元素位置,缩到长度1区间就确定了答案,无需再处理 | 循环终止时left==right,返回该位置元素/索引 |
| 类型B:推导结果 | 找插入位置/判断元素是否存在(存在返回True/False) | 继续处理,判断后推导最终答案 | while left <= right |
答案是"推导出来的结果"(插入位置/存在与否),长度1区间只是最后一个待检查的元素,需判断后确定最终答案(比如判断插入到该元素左边还是右边) | 循环终止时left > right,返回left(插入位置)/False(不存在) |
【之前的关键误区】:曾误以为「找唯一值」是判断标准(比如searchMatrix看似找target位置,就该用left<right),实际纠正:
- 误区本质:searchMatrix的核心是「推导target是否存在」(类型B),而非「单纯找target位置」,因此仍用left<=right;
- 插入位置的"唯一性"≠"数组已有元素位置":插入位置是推导的唯一位置(比如nums=[1,3,5],target=4的插入位置2),而非数组里本就有的元素位置,因此需处理left==right。
三、解题三步法(套用到所有二分题,彻底解决循环条件选择疑惑)
步骤1:判断题型类型(核心,解决循环条件选择的根本)
- 问自己:最终答案是「数组里已有的元素位置」(类型A),还是「推导出来的结果」(类型B)?
- 类型A(找边界/定值):旋转数组最小值、第一个大于target的元素、峰值位置、最后一个小于target的元素;
- 类型B(推导结果):插入位置、判断元素是否存在(二维矩阵searchMatrix)、找target索引(不存在返回-1)。
步骤2:确定循环条件(直接对应left==right的处理方式)
【核心疑惑3】:循环条件和left==right的关系?
- 类型A →
while left < right:长度1区间(left==right)时终止,不进入循环处理; - 类型B →
while left <= right:长度1区间(left==right)时仍满足条件,继续进入循环处理。
步骤3:边界更新(保证区间自洽,避免死循环)
【避坑疑惑】:为什么找最小值时right=mid,找插入位置时right=mid-1?
- 通用原则:想排除mid →
left=mid+1;想保留mid(mid可能是答案)→right=mid; - 类型A注意:更新时避免
right=mid-1(易漏掉mid这个答案,比如找最小值时mid可能就是最小值); - 类型B注意:更新时必须
left=mid+1/right=mid-1(排除已检查的mid,否则无法缩小到空区间)。
四、常见避坑点(对应疑惑的易错点)
- 区间定义与更新不一致(最易踩坑):
- 用
while left <= right(闭区间)却更新right=mid→ 死循环(left==right时mid不变,边界不更新); - 用
while left < right(左闭右开)却初始化right=len(nums)-1→ 漏查元素;
- 用
- 类型A误用
left <= right:长度1区间时进入循环,mid不变导致死循环(比如找最小值时); - 类型B更新时未±1:比如
left=mid,导致区间无法缩小到空,死循环; - 混淆「有效区间」:开区间写法中
right=len(nums),有效区间是[left, right-1],但核心逻辑仍围绕left==right=长度1区间。
五、经典例题拆解(对应疑惑的实战验证)
例1:类型A(找旋转数组最小值,验证「为什么用left<right」)
【核心疑惑验证】:缩到left==right就确定最小值,无需处理
python
def findMin(nums):
left, right = 0, len(nums)-1
while left < right: # 类型A:到left==right就停
mid = (left+right)//2
if nums[mid] > nums[right]:
left = mid + 1 # 排除mid(mid在左半大数组,不可能是最小值)
else:
right = mid # 保留mid(mid可能是最小值,避免漏判)
return nums[left] # left==right,直接返回(无需处理)
例2:类型B(找插入位置,验证「为什么用left<=right」)
【核心疑惑验证】:left==right时需处理,判断插入方向
python
def searchInsert(nums, target):
left, right = 0, len(nums)-1
while left <= right: # 类型B:left==right时继续处理
mid = (left+right)//2
if nums[mid] < target:
left = mid + 1 # 排除mid,插入位置在右侧
else:
right = mid - 1 # 排除mid,插入位置在左侧
return left # left>right,返回推导的插入位置
例3:类型B(searchMatrix,验证「看似找位置却用left<=right」)
【误区纠正验证】:核心是推导存在性,而非找位置
python
def searchMatrix(matrix, target):
m,n=len(matrix),len(matrix[0])
left,right=0,m*n-1
while left<=right: # 类型B:推导存在性,需处理left==right
mid=(left+right)//2
if matrix[mid//n][mid%n]==target:
return True # 找到位置,推导"存在"
if matrix[mid//n][mid%n]<target:
left=mid+1
else:
right=mid-1
return False # left>right,推导"不存在"
六、核心总结(直击所有疑惑的本质)
- 二分法的核心不是「区间写法」(闭/开),而是「是否处理长度1的区间(left==right)」;
- 类型A(找定值/边界):缩到left==right就停(用left<right),直接取答案;
- 类型B(推导结果):处理left==right(用left<=right),推导最终答案;
- 所有疑惑的根源:混淆了「答案是数组已有位置」和「答案是推导结果」,抓住这一点就能秒选循环条件。