二分查找基础原理与题目说明
文章目录
- 二分查找基础原理与题目说明
-
- [一、 什么是二分查找?](#一、 什么是二分查找?)
- [二、 经典二分查找应用](#二、 经典二分查找应用)
-
- [[35. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/)](#35. 搜索插入位置)
- [[34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)](#34. 在排序数组中查找元素的第一个和最后一个位置)
- [三、 二维矩阵中的二分查找](#三、 二维矩阵中的二分查找)
-
- [[74. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/)](#74. 搜索二维矩阵)
- [[240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/)](#240. 搜索二维矩阵 II)
- [四、 旋转排序数组系列](#四、 旋转排序数组系列)
-
- [[33. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)](#33. 搜索旋转排序数组)
- [[153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)](#153. 寻找旋转排序数组中的最小值)
🔗 查看完整专栏(LeetCode基础算法专栏)
点击阅读:Python 数据结构与语法速查笔记
点击阅读:哈希表基础原理与题目说明
点击阅读:双指针基础原理与题目说明
点击阅读:滑动窗口基础原理与题目说明
点击阅读:队列与单调队列基础原理与题目说明
点击阅读:二分查找基础原理与题目说明
点击阅读:矩阵基础原理与题目说明
特别说明:
本文为个人的 LeetCode 刷题与学习笔记,内容仅供学习与交流使用,禁止转载或用于商业用途。需要强调的是,文中的题目解法不一定是最优解(可能存在时间或空间复杂度的进一步优化空间),主要目的是分享个人的解题思路与逻辑实现,仅供参考。 笔记内容为个人理解与总结,可能存在疏漏或偏差,欢迎读者自行甄别并交流探讨。

一、 什么是二分查找?
二分查找(Binary Search)是基于分治(减治)思想 的高效查找算法。针对有序区间,通过「每轮排除一半搜索空间」的方式,将查找效率从线性查找的 O ( n ) O(n) O(n) 飞跃提升到 O ( log n ) O(\log n) O(logn)。
核心前提:
- 数据有序(或具有某种可以明确切分的局部有序性/单调性)。
- 支持随机访问(底层必须是数组,链表等无法直接通过索引访问的结构不适用)。
二、 经典二分查找应用
35. 搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。要求 O ( log n ) O(\log n) O(logn) 的时间复杂度。
解题思路:
标准的**「找最左侧答案 / 插入位置」**模板题。
初始化左闭右开区间 [0, len(nums)),这样能完美覆盖 target 大于所有元素需要插入到数组末尾的情况。每次判断 nums[mid] < target,若是则 left = mid + 1,否则 right = mid,最终 left 就是插入位置。
核心代码:
py
from typing import List
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) # 左闭右开区间
while left < right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # 目标在右侧,跳过 mid
else:
right = mid # 目标在左侧,保留 mid 作为候选插入点
# 循环结束时 left == right
return left
34. 在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果不存在,返回 [-1, -1]。
解题思路:
这道题是左/右边界模板的完美结合:
- 第一次二分 :找第一个 ≥ t a r g e t \ge target ≥target 的位置(左边界),直接套用「找最左侧」模板。
- 第二次二分 :找最后一个 ≤ t a r g e t \le target ≤target 的位置(右边界),直接套用「找最右侧」模板。
- 最后通过验证边界的合法性与元素匹配性,判断目标值是否存在。
核心代码:
py
from typing import List
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums:
return [-1, -1]
# 1. 找第一个 >= target 的数值 ------ 锁定最左边界
left = 0
right = len(nums)
while left < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
start_idx = left
# 2. 找最后一个 <= target 的数值 ------ 锁定最右边界
left = 0
right = len(nums) - 1
while left < right:
mid = (left + right + 1) // 2 # 必须 +1 防死循环
if nums[mid] > target:
right = mid - 1
else:
left = mid
end_idx = left
# 3. 校验最终找到的区间是否合法且匹配 target
if start_idx > end_idx or start_idx >= len(nums) or nums[end_idx] != target:
return [-1, -1]
return [start_idx, end_idx]
三、 二维矩阵中的二分查找
74. 搜索二维矩阵
题目描述:
给你一个 m × n m \times n m×n 整数矩阵。每行从左到右递增,且每行的第一个整数大于前一行的最后一个整数。判断目标值是否存在于矩阵中。
解题思路:
选用两次二分查找解题,充分利用矩阵特性。
- 第一次二分 :定位 target 可能所在的行。本质是找最后一个行首 ≤ t a r g e t \le target ≤target 的行,这属于「找最右侧答案」,套用对应模板。
- 第二次二分 :在目标行内判断目标值是否存在,这属于普通的二分查找判定。整体时间复杂度 O ( log ( m ) + log ( n ) ) = O ( log ( m n ) ) O(\log(m) + \log(n)) = O(\log(mn)) O(log(m)+log(n))=O(log(mn))。
核心代码:
py
from typing import List
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
# 1. 第一次二分:定位 target 所在的行
left = 0
right = len(matrix) - 1
while left < right:
mid = (left + right + 1) // 2 # 找最右侧满足条件的行,加1防死循环
if matrix[mid][0] > target:
right = mid - 1
else:
left = mid
row = left # 锁定目标行
# 2. 第二次二分:在目标行中查找 target
left = 0
right = len(matrix[0]) - 1 # 此处采用精确匹配的闭区间写法
while left <= right:
mid = (left + right) // 2
if matrix[row][mid] == target:
return True
elif matrix[row][mid] < target:
left = mid + 1
else:
right = mid - 1
return False
240. 搜索二维矩阵 II
题目描述:
矩阵每行从左到右升序,每列从上到下升序。搜索目标值。
解题思路:
注:本题有从右上角出发 O ( m + n ) O(m+n) O(m+n) 的解法,此处分享基于二分思想的解法。
逐行遍历,由于每一行都是严格升序的,可以直接对每一行套用标准的「左闭右闭」一维二分模板。虽然不是最优的时间复杂度,但逻辑清晰、健壮。
核心代码:
py
from typing import List
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]:
return False
n, m = len(matrix), len(matrix[0])
for i in range(n):
left, right = 0, m - 1
while left <= right:
mid = (left + right) // 2
if matrix[i][mid] == target:
return True
elif matrix[i][mid] < target:
left = mid + 1
else:
right = mid - 1
return False
四、 旋转排序数组系列
旋转数组打破了全局的有序性,但保留了局部有序性 。二分的难点在于如何通过 mid 判断目标值落在左半段还是右半段。
33. 搜索旋转排序数组
题目描述:
整数数组 nums 按升序排列且互不相同,在某个下标上进行了旋转。求目标值 target 的下标。要求 O ( log n ) O(\log n) O(logn) 复杂度。
解题思路:
原数组旋转后,通过 mid 切开,必定有一半区间是严格升序的。
- 判断哪一半有序 :通过判断
nums[left] <= nums[mid],若成立,说明[left, mid]有序;否则[mid, right]有序。 - 判断 target 是否在有序区间内:如果在有序的那一半区间内,直接将边界收缩到该区间;如果不在,则说明在另一半乱序区间内,收缩到另一半。
核心代码:
py
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 1. 左半区间 [left, mid] 是严格有序的
if nums[left] <= nums[mid]:
# 检查 target 是否在有序的左半区间内
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# 2. 否则,右半区间 [mid, right] 是严格有序的
else:
# 检查 target 是否在有序的右半区间内
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
153. 寻找旋转排序数组中的最小值
题目描述:
找出并返回旋转后升序数组中的最小元素。要求 O ( log n ) O(\log n) O(logn)。
解题思路:
旋转后数组被分为两段连续的升序子数组,且前一段的所有元素必定大于后一段的所有元素。最小值就是这两段的分界点。
我们将 nums[mid] 与右边界 nums[right] 进行比较:
- 若
nums[mid] < nums[right]:说明右侧是严格递增的,最小值一定在左侧或就是mid,故right = mid。 - 若
nums[mid] > nums[right]:说明出现了断层,最小值一定在mid右侧,故left = mid + 1。
核心代码:
py
from typing import List
class Solution:
def findMin(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left < right:
mid = (left + right) // 2
# 若 mid 小于最右侧元素,说明 mid 到 right 是递增的
# 最小值必然在 left 到 mid 之间
if nums[mid] < nums[right]:
right = mid
# 若 mid 大于最右侧,说明断层在右边
else:
left = mid + 1
return nums[left]