二分搜索
在有序数组中确定num存在还是不存在:
- 当
arr[m] == num
,则num存在 - 当
arr[m] > num
,则r = m - 1
,缩小r的范围,继续往左二分 - 当
arr[m] < num
,则l = m + 1
,缩小l的范围,继续往右二分
java
// 保证arr有序,才能用这个方法
public static boolen exist(int[] arr, int num) {
if (arr == null || arr.length == 0){
return false;
}
int l = 0, r = arr.length - 1, m = 0;
while (l <= r) {
// 中间值
m = l + ((r - l) >> 1);
if (arr[m] == num) {
// 中间值 == num,直接返回结果
return true;
} else if (arr[m] > num) {
// 中间值 > num,缩小r的范围
r = m - 1;
} else {
// 中间值 < num, 缩小l的范围
l = m + 1;
}
}
return false;
}
有序数组中找>=num的最左位置:
ans(二分搜索法答案)
初始值设置为:-1,-1
表示不存在符合要求的值- 当
middle(中点值) >= num 时
修改ans = middle 并 r = middle - 1 缩小右边范围
,继续往左二分 - 当
middle(中点值) < num 时
不修改ans 但 l = middle + 1 缩小左边范围
,继续往右二分 - 求中间值公式用:
l + ((r - l) >> 1) 或者 l + ((r - 1) / 2)
,防止运算数值溢出 - 找<=num的最左位置没有意义,直接找下标为0的位置进行判断就可以得出结果
java
// 保证arr有序,才能用这个方法
// 有序数组中找>=num的最左位置
public static int findLeft(int[] arr, int num) {
int l = 0, r = arr.length - 1, m = 0;
int ans = -1;
while (l <= r){
m = l + ((r - l) >> 1); // 等于 l + ((r - 1) / 2), 等于 (l + r) / 2
if (arr[m] >= num) {
ans = m;
r = m - 1;
} else {
l = m + 1;
}
}
return ans;
}
在有序数组中找<=num的最右位置:
ans(二分搜索法答案)
初始值设置为:-1,-1
表示不存在符合要求的值- 当
middle(中点值) <= num 时
修改ans = middle 并 l = middle + 1 缩小右边范围
,继续往右二分 - 当
middle(中点值) > num 时
不修改ans 但 r = middle - 1 缩小左边范围
,继续往左二分 - 求中间值公式用:
l + ((r - l) >> 1) 或者 l + ((r - 1) / 2)
,防止运算数值溢出
java
// 保证arr有序,才能用这个方法
// 有序数组中找<=num的最右位置
public static int findLeft(int[] arr, int num) {
int l = 0, r = arr.length - 1, m = 0;
int ans = -1;
while (l <= r){
m = l + ((r - l) >> 1); // 等于 l + ((r - 1) / 2), 等于 (l + r) / 2
if (arr[m] <= num) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
return ans;
}
二分搜索不一定发生在有序数组上(比如寻找峰值问题):
-
峰值:
i-1 < i > i+1
,则i
为峰值,如果右边或者左边没数值可以认为是无穷小 -
数组条件:数组中相邻的两个数不相等, 只返回一个峰值就行
-
0位置不是峰值,N-1位置不是峰值,则呈现左边上扬右边下降的趋势,这中间会出现一个或者多个峰值
-
计算步骤:
- 先判断
数组[0]
是不是峰值,是则直接返回 - 再判断
数组[N-1]
是不是峰值,是则直接返回 - 设置 L = 1,R = N-2,求中间值,如果中间值是峰值则直接返回
- 判断中间值的大小,当
左侧数值>中间值,往左侧二分
,而右侧数值>中间值,往右侧二分
,如果两个都成立选其中一个就行
java// 峰值元素是指其严格大于左右相邻值的元素 // 给你一个整数数组 nums,已知任何两个相邻的值都不相等 // 找到峰值元素并返回其索引 // 数组可能包含多个峰值,在这种情况下,返回任何一个峰值 所在位置即可 // 你可以假设 nums[-1] = nums[n] = 无穷小 // 你必须实现时间复杂度为 0(log n) 的算法来解决此问题 public static int findPeakElement(int[] arr) { int n = arr.length; // 小 小 // -1 0 1 if (arr.length == 1) { return 0; } // 数组长度 >= 2 // 单独验证0位置,是不是峰值点 if (arr[0] > arr[1]) { return 0; } // 单独验证n-1位置,是不是峰值点 if (arr[n - 1] > arr[n - 2]) { return n - 1; } // X 中间一定有一个峰值 X // 0 N-1 // 中间 :1 ~ n-2 ,一定有峰值点 // 每一步的l...r : 一定有峰值点 int l = 1, r = n - 2, m = 0, ans = -1; while (l <= r) { m = l + ((r - l) >> 1); if (arr[m - 1] > arr[m]) { r = m - 1; } else if (arr[m] < arr[m + 1]) { l = m + 1; } else { ans = m; break; } } return ans; }
- 先判断
某侧必有对应的值或者某侧必没有对应的值,则可以使用二分搜索法
如果数组长度为n,那么二分搜索搜索次数是log n次,以2为底
二分搜索世界复杂度o(log n)