-
请牢记二分查找算法的时间复杂度为O(log2n)
-
二分查找要求线性表必须以顺序结构存储,并且表中元素按关键字有序排列。
-
二分查找一般设置2个指针,一个指针left指向数组开头,另一个指针right指向数组末尾。查找循环的有效条件为left<=right
。
for(int left=0, right=n-1;left<=right;) {...}
-
每次循环需要对left
和right
中间一位进行判断。以C++为例,由于除法运算符/会将整数除法结果自动舍去小数点,因此每次的目标为left
和right
的中间位(right-left
结果为偶数)或中间偏左一位(right-left
结果为奇数)。
中间位mid
的计算有两种方式:
?
int mid = (right + left) >> 1;
或
int mid = ((right - left) >> 1) + left;
将式1转化为数学公式如下:
m
i
d
=
l
e
f
t
2
+
r
i
g
h
t
2
mid=\frac{left}{2}+\frac{right}{2}
mid=2left?+2right?
式1非常易懂,将两数相加除以2即为他们的中位数。但是这个写法已经这么简单了,为什么还会出现式2呢?
原因是1式会产生数值溢出。我们粗暴一点,假设当前left
和right
皆为INT_MAX
,即7FFFFFFFH
,则两数相加就超出了int能表示的范围上限,最终mid的值就不正确了。将式2转换为数学公式如下:
m
i
d
=
r
i
g
h
t
2
?
l
e
f
t
2
+
l
e
f
t
mid=\frac{right}{2}-\frac{left}{2}+left
mid=2right??2left?+left
可以看到只是进行了一次简单的改变,但式2是不会出现数值溢出问题的。
当然1,在Python等语言中基本上是不会出现这种问题的,可以不用考虑。不过以这些语言为主力语言的小伙伴了解一下这个问题还是有益的。
当然2,你要这么写也没人拦你(也确实能出正确结果:
int mid = (long long(left) + long long(right)) >> 1;
但万一left
和right
本身就是双长整型的最大值呢?即使mid
定义为long long最终也会产生数值溢出的。所以无论怎样还是要用到式2的。
-
left
和right
在什么时候变化可能会根据实际情况改变,这里仅记录最基础的改变时机。
for (int left = 0, right = n - 1; left <= right;)
{
int mid = ...;
if (target == nums[mid])
return mid;
else if (target < nums[mid])
right = mid - 1;
else
left = mid + 1;
}
-
二分查找不仅可以用于查找确切值,还能查找到比某个数大,或比某个数小的临界位置。
(这里指的当然不是找到那个数target然后左边就比它小,右边就比它大的这种显而易见的问题。)
在基础的算法结构上修改一些细节即可达到此目的。 参考案例[278. 第一个错误的版本]和以下案例:
int left = 0, right = nums.size() - 1;
int sub = nums.size();
while (left <= right)
{
int mid = ((right - left) >> 1) + left;
if (target < nums[mid])
{
sub = mid;
right = mid - 1;
}
else
{
left = mid + 1;
}
}
上述代码在循环结束后,sub为数组nums
中第一个比target
的值的下标。