LeetCode 专题 – 二分查找专题
题目
- 33搜索旋转排序数组
- 34在排序数组中查找元素的第一个和最后一个位置
- 35搜索插入位置
- 69x的平方根
- 74搜索二维矩阵
- 153寻找旋转排序数组中的最小值
- 162寻找峰值
- 275H指数II
- 278第一个错误的版本
- 287寻找重复数
二分查找总结
大白话:
- 统一模板,刷题运用
二分查找、堆、二叉树的操作时间复杂度都是O(logn),有时甚至比O(1)的算法还要高效。
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0.
二分查找思想很简单,但是细节需要把握。我也参照过labuladong的二分模板,也看过wangzheng的讲解。虽然labuladong的模板代码结构上更好看,但是wangzheng的模板更不容易出错。
本文就以wangzheng的模板为基础,总结一下基础二分查找及其四个变体的代码模板。
重点:
- 循环条件都是
low <= high
- mid的更新分别位
mid = high - 1
或者mid = low + 1
最简单的二分查找
最简单的情况就是有序数组中不存在重复元素,也是各种二分变种的基础
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (a[mid] == value) {
return mid;
} else if (a[mid] < value) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
关键点:
- 循环条件是 low<=high
- mid = low + (high - low) / 2,防止溢出
- low = mid + 1, high = mid - 1,防止死循环
局限性:
- 二分查找依赖的是顺序表结构,简单点说就是数组
- 二分查找针对的是有序数据
- 数据量太小不适合二分查找
- 数据量太大也不适合二分查找
变体一:查找第一个值等于给定值的元素
public int bsearch(int[], int n, int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(a[mid] > value) high = mid - 1;
else if (a[mid] < value) low = mid + 1;
else {
if((mid == 0) || (a[mid - 1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
如果 mid 等于 0,那这个元素已经是数组的第一个元素,那它肯定是我们要找的;如果 mid 不等于 0,但 a[mid] 的前一个元素 a[mid-1] 不等于 value,那也说明 a[mid] 就是我们要找的第一个值等于给定值的元素。
如果经过检查之后发现 a[mid] 前面的一个元素 a[mid-1] 也等于 value,那说明此时的 a[mid] 肯定不是我们要查找的第一个值等于给定值的元素。那我们就更新 high=mid-1,因为要找的元素肯定出现在 [low, mid-1] 之间。
变体二:查找最后一个值等于给定值的元素
public int bsearch(int[], int n, int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(a[mid] > value) high = mid - 1;
else if (a[mid] < value) low = mid + 1;
else {
if((mid == n - 1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
}
}
return -1;
}
如果 a[mid] 这个元素已经是数组中的最后一个元素了,那它肯定是我们要找的;如果 a[mid] 的后一个元素 a[mid+1] 不等于 value,那也说明 a[mid] 就是我们要找的最后一个值等于给定值的元素。
如果我们经过检查之后,发现 a[mid] 后面的一个元素 a[mid+1] 也等于 value,那说明当前的这个 a[mid] 并不是最后一个值等于给定值的元素。我们就更新 low=mid + 1,因为要找的元素肯定出现在 [mid+1, high] 之间。
变体三:查找第一个大于等于给定值的元素
public int bsearch(int[], int n, int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(a[mid] >= value) {
if((mid == 0) || (a[mid - 1] < value)) return mid;
else high = mid - 1;
}
else {
low = mid + 1;
}
}
return -1;
}
如果 a[mid] 小于要查找的值 value,那要查找的值肯定在 [mid+1, high] 之间,所以,我们更新 low=mid+1。
对于 a[mid] 大于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 前面已经没有元素,或者前面一个元素小于要查找的值 value,那 a[mid] 就是我们要找的元素。这段逻辑对应的代码是第 7 行。
如果 a[mid-1] 也大于等于要查找的值 value,那说明要查找的元素在 [low, mid-1] 之间,所以,我们将 high 更新为 mid-1。
变体四:查找最后一个小于等于给定值的元素
public int bsearch(int[], int n, int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(a[mid] <= value) {
if((mid == n - 1) || (a[mid + 1] > value)) return mid;
else low = mid + 1;
}
else {
high = mid - 1;
}
}
return -1;
}
对于 a[mid] 小于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 后面已经没有元素,或者后面一个元素大于要查找的值 value,那 a[mid] 就是我们要找的元素。
如果 a[mid+1] 也小于等于要查找的值 value,那说明要查找的元素在 [mid+1, high] 之间,所以,我们将 low 更新为 mid+1。
如果 a[mid] 大于要查找的值 value,那要查找的值肯定在 [low, mid-1] 之间,所以,我们更新 high=mid-1。
接下进行实战
大雪菜选的LeetCode十道题目。
69,35,34,74,153,33,278,162,287,275
我把这十题全部按照这个模板来,并且ac了,大家可以看代码感受一下。