Java 二分查找(Binary Search) 是算法面试和刷题中最核心、最常考的技巧之一。
它将查找时间从 O(n) 降到 O(log n),但写对细节非常考验边界处理能力。
下面我给你一套由浅入深的练习路线 + 经典题目 + Java 代码模板 + 常见变体 + 易错点总结,适合从入门到精通。
1. 二分查找核心模板(必背三种写法)
模板1:标准查找(找任意一个位置)
(LeetCode 704 模板)
// 返回 target 的索引,不存在返回 -1
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意:right 是闭区间
while (left <= right) { // 注意:<=
int mid = left + (right - left) / 2; // 防溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
模板2:查找左边界(第一个 >= target 的位置)
(最常用变体)
// 返回 >= target 的最左位置,找不到返回 nums.length
public int leftBound(int[] nums, int target) {
int left = 0;
int right = nums.length; // 注意:开区间,right 可取到 length
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid; // 收缩右边界
}
}
return left; // left 就是答案
}
模板3:查找右边界(最后一个 <= target 的位置)
public int rightBound(int[] nums, int target) {
int left = 0;
int right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1; // 收缩左边界
} else {
right = mid;
}
}
return left - 1; // 注意 -1
}
一句话记忆:
- 找任意位置 → while(left <= right),找到就返回
- 找左边界 → 收缩右边界(right = mid),最后 left 就是答案
- 找右边界 → 收缩左边界(left = mid + 1),最后 left – 1 就是答案
2. 推荐练习顺序(由易到难,LeetCode 编号)
阶段1:基础(掌握模板)
- 二分查找(Easy)
- 搜索插入位置(Easy) ← 就是左边界模板
- 第一个错误的版本(Easy) ← 经典左边界
阶段2:左右边界 + 计数
-
- 在排序数组中查找元素的第一个和最后一个位置(Medium) ← 必做!
- 704 的变形:统计某个数出现次数(用左右边界相减 + 1)
阶段3:旋转数组 + 特殊二分
- 搜索旋转排序数组(Medium)
- 寻找旋转排序数组中的最小值(Medium)
- 搜索旋转排序数组 II(Medium) ← 有重复元素
- 寻找旋转排序数组中的最小值 II(Hard) ← 有重复
阶段4:二维矩阵 + 变种
- 搜索二维矩阵(Medium) ← 先找行,再找列
- 搜索二维矩阵 II(Medium) ← 从右上角开始的“伪二分”
- 有序矩阵中第 K 小的元素(Medium)
阶段5:二分答案(最重要进阶)
- 爱吃香蕉的珂珂(Medium) ← 经典二分答案
- 在 D 天内送达包裹的能力(Medium)
- 分割数组的最大值(Hard)
- 分享巧克力(Hard)
- 制作 m 束花所需的最少天数(Medium)
阶段6:综合 / 变形
- x 的平方根(Easy) ← 浮点二分或整数二分
- 寻找峰值(Medium) ← 二分 + 单调性
- 山脉数组的峰顶索引(Medium)
- 4. 寻找两个正序数组的中位数(Hard) ← 二分 + 划分
3. 经典题目 Java 完整代码示例(34题)
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ans = new int[]{-1, -1};
if (nums == null || nums.length == 0) return ans;
// 找左边界
int left = leftBound(nums, target);
if (left == nums.length || nums[left] != target) {
return ans;
}
// 找右边界
int right = rightBound(nums, target);
ans[0] = left;
ans[1] = right;
return ans;
}
private int leftBound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
private int rightBound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else {
right = mid;
}
}
return left - 1;
}
}
4. 常见易错点 & 防坑指南
- while (left <= right) 还是 < ?
→ 标准找元素用 <=,边界用 < - mid = (left + right) / 2 还是 left + (right – left)/2 ?
→ 永远用后者,防止 int 溢出 - right = nums.length 还是 length-1 ?
→ 边界模板用 length(开区间思想) - 找左边界时 if (nums[mid] < target) left = mid+1 else right = mid
→ 不要写成 <=,否则会错 - 旋转数组怎么判断哪边有序?
→ 看 nums[mid] 和 nums[right] 的关系 - 二分答案时,check 函数写错方向最常见
→ 记住:能行 → 尝试更小;不行 → 尝试更大
5. 练习建议
- 先把 704、35、34 三题手写 10 遍(不同写法)
- 然后做 33、153、875 这三道经典变形
- 每天做 2-3 道二分题,坚持 2 周
- 强烈推荐看 labuladong 的《二分查找详解》系列文章(非常系统)
有哪一道题想看详细题解或代码,或者想针对某个变体(旋转、答二分、峰值、二维)深入练习,告诉我,我可以给你单独出题或解析!加油刷题!