文章目录
二分查找 = 折半查找
时间复杂度O(logN)
最标准的查找
-
初始化 左边界
int len = arr.length - 1; int l = 0; int r = len-1;
-
while循环判断条件
这个是 左闭右闭区间 (我常用的)
while (left <= right)
还有 左闭右开 (不常用)下面说
-
获取 区间的中间坐标 mid
int mid = l + (r - l)/2; // 为什么不能写 (l+r)/2? // 因为 int l = Integer.MAX_VALUE-1; int r = Integer.MAX_VALUE-1; // 那这个时候 l+r会出现溢出
-
左右边界的确定
if(nums[mid] == target){ return mid; }else if(nums[mid] < target){ l = mid+1; }else(target < nums[mid]){ r = mid-1; }
二分查找的两大基本原则
由👆可见,
- 二分查找每次都要 缩减搜索区域,左右边界的缩小
- 缩减方式要合理,不能将潜在答案缩减出去
当已知存在target
时,我们可以直接通过 大小判断 和 左右边界 = mid±1获得,因为这种判断 和缩减方式 都是没有问题的,但是若出现 变体 的话,则需要 适当更改缩减方式
变形一:(已知存在目标值)最左边的目标值
// 查找第一个值等于给定值的元素
private int firstEquals(int[] arr, int target) {
int l = 0, r = arr.length - 1;
while (l < r) {
int mid = l + ((r - l) >> 1);
if (arr[mid] < target) l = mid + 1;
else r = mid; // 收缩右边界不影响 first equals
}
if (arr[l] == target && (l == 0 || arr[l - 1] < target)) return l;
return -1;
}
变形二:(已知存在目标值)最右边的目标值
// 查找最后一个值等于给定值的元素
private int lastEquals(int[] arr, int target) {
int l = 0, r = arr.length - 1;
while (l < r) {
int mid = l + ((r - l + 1) >> 1);
if (arr[mid] > target) r = mid - 1;
else l = mid; // 收缩左边界不影响 last equals
}
if (arr[l] == target && (l == arr.length - 1 || arr[l + 1] > target)) return l;
return -1;
}
变形四:大于目标值的第一个数
744. 寻找比目标字母大的最小字母 - 力扣(LeetCode) (leetcode-cn.com)
public char nextGreatestLetter(char[] letters, char target) {
int l = 0;
int r = letters.length-1;
if(letters[r]<=target){
return letters[l];
}
while(l<r){
int mid = l + (r-l)/2;
if(letters[mid]<=target){
l = mid+1;
}else{
r = mid;
}
}
return letters[l];
}
变形五:大于等于目标值的第一个数
👇:若存在目标值 则返回 最左边的目标值 即 第一个目标值
若不存在目标值 则返回 大于目标值的第一个数下标
若整个数组都小于目标值 则返回 nums.length
35. 搜索插入位置 - 力扣(LeetCode) (leetcode-cn.com)
// 查找第一个大于等于给定值的元素
public int searchInsert(int[] nums, int target) {
int l =0;
int r = nums.length;
while(l<r){
int mid = l+(r-l)/2;
if(nums[mid]>=target){
r = mid;
}else{
l = mid+1;
}
}
return l;
}
变形七:小于目标值的最后一个数
剑指 Offer 53 - I. 在排序数组中查找数字 I - 力扣(LeetCode) (leetcode-cn.com)
需要注意!需要注意!需要注意!需要注意! 👇
因为初始值l=0
,若整个nums ≥ target
,那么 最后的 l还是0 l=0
不会变。
即 if(l==0)
需要判断一下 nums[l]
和 target
的大小关系
private int find (int[] nums,int target) {
// 小于目标值的最后一个数
int l =0;
int r =nums.length-1;
while(l<r){
int mid = l+(r-l+1)/2;
if(nums[mid]>=target){
//当中间值 ≥ 目标值 因为我们不需要目标值 所以要缩减 即r = mid -1;
r = mid-1;
}else{
l = mid;
}
}
return l;// 若整个 nums ≥ target 那么l=0;
}
变形三:小于等于目标值的最后一个数
情况一: 存在目标值 会输出 最后一个目标值
**情况二:**不存在目标值 会输出 小于目标值的最后一个数
需要注意!需要注意!需要注意!需要注意! 👇
// 查找最后一个小于等于给定值的元素
private int lastLessOrEquals(int[] nums, int target) {
int l = 0;
int r = nums.length - 1;
while (l < r) {
int mid = l + (r - l + 1)/2;
if (nums[mid] > target){//我要查找 小于等于target的元素 那么 大于target的需要全部删除
r = mid - 1;
}
else l = mid; //nums[mid] <= target
}
// 注意 l 可能不变 需要最后做个对比
if (arr[l] <= target && (l == arr.length - 1 || arr[l + 1] > target)) return l; // <=
return -1;
}