二分查找介绍
二分查找算法是一种用于在有序数组或列表中查找特定元素的方法。它通过反复将搜索空间一分为二,直到找到目标元素或确定其不存在。
二分查找算法
需求 :在有序数组 a 内,查找值 target
- 如果找到返回索引
- 如果找不到返回 -1
java
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) { // 在左边
j = m - 1;
} else if (a[m] < target) { // 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
binarySearch
方法接受一个数组a
和目标值target
作为参数。它初始化两个指针 i 和 j ,分别指向数组的起始和结束索引。- 算法进入一个
while
循环,在循环中,直到 i 指针大于 j 指针才会停止。在每次迭代中,它计算中间索引m
,并将该索引处的元素与目标值进行比较。 - 如果不加
i==j
行不行? 不行,因为这意味着i,j
指向的元素会漏过比较 - 计算中间所以这里建议使用无符号右移
>>>
, 向右边移动一位,相当于除以2。如果使用(i + j)/2
,i和j两个数很大相加,会超过int的范围,导致中间值变为负数。 - 如果 m 处的元素等于目标值,方法返回索引 m ,表示找到了目标元素。如果 m 处的元素小于目标值,则将搜索范围缩小为数组的右半部分,更新 i 指针。相反,如果 m 处的元素大于目标值,则将搜索范围缩小为数组的左半部分,更新 j 指针。
- 如果 while 循环完成而未找到目标元素,则方法返回 -1,表示在数组中未找到该元素。
二分查找时间复杂度
- 黑色横线
O(1)
,常量时间,意味着算法时间并不随数据规模而变化 - 绿色
O(log(n))
,对数时间 - 蓝色
O(n)
,线性时间,算法时间与数据规模成正比 - 橙色
O(n*log(n))
,拟线性时间 - 红色
O(n^2)
平方时间 - 黑色朝上
O(2^n)
指数时间
二分查找是一种高效的算法,其时间复杂度为 O(log n)
,其中 n 是数组中元素的个数。
时间复杂度
- 最坏情况:
O(log n)
- 最好情况:如果待查找元素恰好在数组中央,只需要循环一次
O(1)
空间复杂度
- 需要常数个指针 i,j,m,因此额外占用的空间是
O(1)
二分查找进阶
前面的需求都是找到一个满足条件的数据,现在需要返回相同元素最左侧的元素,比如[1, 4, 4, 5, 6]
,返回第一个4的索引位置,该如何实现呢?
java
public static int binarySearchLeftmost1(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
candidate = m; // 记录候选位置
j = m - 1; // 继续向左
}
}
return candidate;
}
同样返回相同元素最右边的索引就不赘述了。
二分查找活学活用
题目:LC2594. 修车的最少时间
最近leetcode上推荐了一道题目,挺有意思的,大家可以思考下利用上面的知识如何解决。
思路
- 如果时间为
t
, 根据题目能力值为r
的机械工可以在r * n2
分钟内修好 n 辆车,可得每个机械工修理的汽车数量是Math.sqrt(t/r)
。 - 假设最小时间10分钟,如果满足
Math.sqrt(10/4) + Math.sqrt(10/2) +...+ Math.sqrt(10/1)>cars数量
,意味着10分钟内修理10辆车绰绰有余,那么我尝试9分钟,知道找到最小的时间。 - 这种存在单调性,我们可以使用二分法方法,左边从0或者1开始,右边从某一台花费的时间
ranks[0]*cars*cars
作为上界。
代码
java
public long repairCars(int[] ranks, int cars) {
// high必须要加1L,不然两个很大cars相乘int存不下
long low = 0, high = 1L * ranks[0] * cars * cars;
while (low <= high) {
long mid = (low + high) >>> 1;
long fixedCars = 0;
for (int rank : ranks) {
fixedCars += (long)Math.sqrt(mid / rank);
}
// 时间满足条件
if(fixedCars >= cars) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
- 注意这里上界限必须乘以1L,否则
cars
很大时int无法存储,出现负数的情况。 - 时间复杂度:其中 n 为
ranks
的长度,L
为二分的上界。 - 空间复杂度:,过程中仅用到常数个变量。