1.2.1 二分算法可以解决的问题
二分的本质是临界点,给定某个区间,在区间上定义了某种性质,使得整个区间一分为二,一般区间满足性质,另一半不满足性质,那么二分可以寻找性质的边界,既可以寻找左边界,也可以寻找右边界。
二分不仅限于有序序列的查找,但有序序列是最常见的一种情况。
arr = [3, 27, 15, 48, 90, 6, 2, 17, 54, 11] |
---|
在arr这个序列中,6是临界点,6左边的数(包括6)都能被3整除,6右边的数都不能被3整除;2也是临界点,2左边的数都能被3整除,2右边的数(包括2)都能不被3整除,二分算法就可以把6和2这两个数查找出来。
1.2.2 二分算法库函数
C++
algorithm头文件下:
- 查找是否存在:
binary_search(first, last, value);
存在返回true
,不存在返回false
,因为不能返回元素具体位置所以用的很少。
- 查找首个大于value的元素的迭代器:
std::upper_bound(first, last, value)
返回指向范围 [first, last) 中首个大于 value 的元素的迭代器,或若找不到这种元素则返回 last,数组返回该位置的指针。
- 查找首个大于等于value的元素的迭代器:
std::lower_bound(first, last, value)
返回指向范围 [first, last) 中首个大于等于 value 的元素的迭代器,或若找不到这种元素则返回 last,数组返回该位置的指针。
Java
数组:Arrays.binarySearch(Object[] a, Object key)
方法
Python
bisect
标准库:
-
bisect.bisect(array, value)
:返回一个位置 pos,如果把 value 插入到 pos 位置的话,那么 pos 左边的所有元素都**小于等于 value,右边的所有元素都大于 **value -
bisect.bisect_left(array, value)
:返回一个位置 pos,如果把 value 插入到 pos 位置的话,那么 pos 左边的所有元素都**小于 value,右边的所有元素都大于等于 **value -
bisect.bisect_left(array, value)
:类似bisect.bisect(array, value)
bisect
标准库的bisect()
类方法都是查找合适的插入位置,bisect
标准库还提供了insort()
类方法在查找到的合适插入位置插入指定的 value 值。
1.2.3 朴素二分:查找指定元素位置
每一次都比较当前区间中点是否满足某个条件,以查找递增序列的整数x为例,当区间中点为x时,返回中点下标,当区间中点大于x时,向区间左半边查找,当区间中点小于x时,向右半边查找,如果迭代结束还没有找到,则返回-1。
二分算法的高效性在于每一步都可以去除当前区间中的一半元素,时间复杂度是 O ( l o g n ) O(logn) O(logn) 。
C++
int biSearch(int l, int r, int x) {
while (l <= r) {
int mid = l + r >> 1;
if (nums[mid] == x) return mid;
else if (nums[mid] > x) r = mid - 1;
else l = mid + 1;
}
return -1;
}
1.2.4 lower:ans是绿色区间的左端点
记区间左、右端点分别为left和right,区间中点为mid,要查找的答案为ans,ans把区间分成左右两部分,左区间为红色区间,右区间为绿色区间,模板一:ans是绿色区间的右端点,这类问题被记为lower类型,C++的algorithm实现了该算法,即 std::lower_bound()
(1)当mid落在绿色区间时,ans一定在mid左边,而且有可能恰好是mid,即ans在区间[left, mid]里;
(2)当mid落在红色区间时,ans一定在mid右边且不包含mid,即ans在区间 [mid + 1, ri