目录
[一、LeetCode 34:在排序数组中查找元素的第一个和最后一个位置](#一、LeetCode 34:在排序数组中查找元素的第一个和最后一个位置)
[Java 完整实现](#Java 完整实现)
[二、LeetCode 74:搜索二维矩阵](#二、LeetCode 74:搜索二维矩阵)
[Java 完整实现](#Java 完整实现)
二刷二分查找,把两道高频变形题放在一起复盘:一道是一维排序数组的区间边界查找 ,一道是二维有序矩阵的降维二分。都是面试常考的二分应用场景,吃透这两道,就能掌握二分的核心变形技巧。
一、LeetCode 34:在排序数组中查找元素的第一个和最后一个位置
题目描述
给定一个升序排列的整数数组 nums 和目标值 target,找出目标值在数组中的开始和结束位置;若不存在,返回 [-1, -1]。要求算法时间复杂度为 O (log n)。
核心思路:两次二分,分别锁定左右边界
普通二分只能找到任意一个等于 target 的位置,这道题的关键是边界控制:
- 找左边界 :当
nums[mid] == target时,继续向左收缩右边界,直到找到第一个等于target的位置; - 找右边界 :当
nums[mid] == target时,继续向右收缩左边界,直到找到最后一个等于target的位置; - 特殊处理 :若左边界不存在(即
nums[left] != target),直接返回[-1,-1]。
Java 完整实现
java
运行
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = findLeftBound(nums, target);
if (left == -1) {
return new int[]{-1, -1};
}
int right = findRightBound(nums, target);
return new int[]{left, right};
}
// 找左边界:第一个等于target的位置
private int findLeftBound(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
// 等于target时,继续往左找,收缩右边界
right = mid - 1;
}
}
// 检查边界是否越界,且值是否匹配
if (left < nums.length && nums[left] == target) {
return left;
}
return -1;
}
// 找右边界:最后一个等于target的位置
private int findRightBound(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
// 等于target时,继续往右找,收缩左边界
left = mid + 1;
}
}
// 检查边界是否越界,且值是否匹配
if (right >= 0 && nums[right] == target) {
return right;
}
return -1;
}
}
复杂度分析
- 时间复杂度:O (log n),两次二分查找,整体仍为对数级复杂度;
- 空间复杂度:O (1),原地操作,无额外空间开销。
二、LeetCode 74:搜索二维矩阵
题目描述
编写高效算法判断 m x n 矩阵中是否存在目标值。矩阵特性:
- 每行从左到右升序排列;
- 每行第一个整数大于前一行的最后一个整数。
核心思路:二维降维,直接二分
这道题的矩阵是严格有序 的,将矩阵按行拼接后就是一个一维升序数组,因此可以直接用普通二分解决,关键是一维索引与二维坐标的转换:
- 一维索引
mid对应的二维行号:row = mid / n(n为矩阵列数); - 一维索引
mid对应的二维列号:col = mid % n。
Java 完整实现
java
运行
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 边界处理:空矩阵/空行/空列
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int m = matrix.length;
int n = matrix[0].length;
int left = 0;
int right = m * n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
// 转换为二维坐标
int row = mid / n;
int col = mid % n;
if (matrix[row][col] == target) {
return true;
} else if (matrix[row][col] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
}
复杂度分析
- 时间复杂度:O (log (m*n)),与普通二分一致,复杂度为对数级;
- 空间复杂度:O (1),原地操作,无额外空间开销。
三、两道题的二分核心对比
表格
| 题目 | 核心考点 | 关键技巧 | 面试高频问题 |
|---|---|---|---|
| 34. 区间边界查找 | 二分的边界变形 | 两次二分,区分左右边界的收缩方向 | 如何处理 target 不存在的情况?左右边界的二分模板怎么写? |
| 74. 二维矩阵搜索 | 二分的降维应用 | 二维坐标与一维索引的转换 | 若矩阵不严格有序(如 "Z" 字形有序),还能用这种方法吗? |
四、二刷复盘感悟
- 二分的核心是「边界控制」 :34 题最容易出错的就是边界处理,尤其是等于
target时的收缩方向,以及循环结束后的边界检查。统一使用「左闭右闭」模板,能大幅降低出错概率; - 二分的本质是「有序数据的快速定位」:只要数据是有序的,不管是一维还是二维,都可以用二分优化。74 题就是典型的将二维有序转化为一维有序,再用二分解决;
- 面试常考变形拓展:除了这两道,「旋转排序数组的二分」「山脉数组的二分」也是高频考点,掌握通用模板就能举一反三