🔍 二分搜索算法详解 | [算法]-[中级]-[查找]
▶ JDK8+ | ⏱️ O(log n)
核心应用场景
- 基本二分搜索:有序数组中判断数字是否存在
- 左边界搜索:有序数组中找>=num的最左位置
- 右边界搜索:有序数组中找<=num的最右位置
- 非有序数组应用:寻找峰值问题(LeetCode 162)
算法实现
/**
* 二分搜索详解 - 左程云算法讲解
* 包含4个核心应用场景的Java实现
*/
/**
* 1. 基本二分搜索:在有序数组中判断num是否存在
* 时间复杂度:O(log n)
*/
class BasicBinarySearch {
public boolean exists(int[] arr, int num) {
if (arr == null || arr.length == 0) return false;
int left = 0;
int right = arr.length - 1;
while (left <= right) { // [关键] 循环条件包含等号
int mid = left + ((right - left) >> 1); // [优化] 防溢出写法
if (arr[mid] == num) {
return true; // 找到目标值
} else if (arr[mid] < num) {
left = mid + 1; // 目标在右半区
} else {
right = mid - 1; // 目标在左半区
}
}
return false; // 未找到
}
}
/**
* 2. 左边界搜索:在有序数组中找>=num的最左位置
* 关键点:找到即记录位置,然后继续向左搜索
*/
class LeftmostSearch {
public int findLeftmost(int[] arr, int num) {
if (arr == null || arr.length == 0) return -1;
int left = 0;
int right = arr.length - 1;
int index = -1; // [注意] 记录满足条件的位置
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] >= num) { // [核心] 大于等于条件
index = mid; // 记录当前满足条件的位置
right = mid - 1; // [关键] 继续向左搜索更优解
} else {
left = mid + 1; // 向右扩大搜索范围
}
}
return index; // 返回最左位置(未找到返回-1)
}
}
/**
* 3. 右边界搜索:在有序数组中找<=num的最右位置
* 关键点:找到即记录位置,然后继续向右搜索
*/
class RightmostSearch {
public int findRightmost(int[] arr, int num) {
if (arr == null || arr.length == 0) return -1;
int left = 0;
int right = arr.length - 1;
int index = -1; // [注意] 记录满足条件的位置
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] <= num) { // [核心] 小于等于条件
index = mid; // 记录当前满足条件的位置
left = mid + 1; // [关键] 继续向右搜索更优解
} else {
right = mid - 1; // 向左缩小搜索范围
}
}
return index; // 返回最右位置(未找到返回-1)
}
}
/**
* 4. 非有序数组应用:寻找峰值问题(LeetCode 162)
* 关键点:利用相对位置决定搜索方向,不依赖全局有序
* 时间复杂度:O(log n)
*
* 问题要求:
* - 峰值元素:严格大于相邻元素
* - 相邻值不相等
* - nums[-1] = nums[n] = 负无穷
* - 可以返回任意峰值位置
*/
class FindPeakElement {
public int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) { // [注意] 用 < 而不是 <=
int mid = left + ((right - left) >> 1);
if (nums[mid] < nums[mid + 1]) {
// [证明] 右侧存在更大值,峰值必在右侧
left = mid + 1;
} else {
// [证明] 当前值可能为峰值或峰值在左侧
right = mid;
}
}
// 最终left==right,即为峰值位置
return left;
}
}
算法可视化
算法要点说明
-
基本二分搜索
- 循环条件:
while(left <= right)
⚠️ 等号不可少 - 本质是排除法(每轮排除一半不可能的区域)
- 循环条件:
-
边界搜索关键差异
- 左边界:找到即左移右指针(记录可能更左的位置)
- 右边界:找到即右移左指针(记录可能更右的位置)
-
峰值问题核心洞见
证明:任意位置mid,至少有一侧存在峰值 - 若 nums[mid] < nums[mid+1]:右侧必存在峰值 (因为nums[n] = -∞,右侧终将下降) - 若 nums[mid] > nums[mid+1]:左侧必存在峰值 (因为nums[-1] = -∞,左侧有上升过程)
-
复杂度分析
- 数学基础:n → n/2 → n/4 → … → 1
- 操作次数 k:n/(2ᵏ) ≈ 1 → k ≈ log₂n
- 故时间复杂度 O(log n)
-
工程注意事项
- 使用
left + ((right - left) >> 1)
防溢出 - 峰值问题用
while(left < right)
避免死循环 - 边界搜索的初始index值设为-1(未找到标识)
- 使用
测试建议
- 基本搜索:测试存在/不存在/边界值情况
- 边界搜索:测试全小于/全大于/跨界情况
- 峰值搜索:测试单峰/双峰/单调递增递减情况
相关题目
🔗 基本二分:LeetCode 704 “二分查找”
🔗 左边界:LeetCode 35 “搜索插入位置”
🔗 右边界:LeetCode 34 “在排序数组中查找元素的第一个和最后一个位置”
🔗 峰值问题:LeetCode 162 “寻找峰值”
效率工具
▶ 二分查找模板生成:
// 快速生成二分查找模板
public int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}