二分查找适用与有序的数据结构,比如有序数组。
二分查找关键点
-
关于闭区间和开区间的定义
如果我能取到这个数那就是闭区间,如果取不到而是取到旁边的数那就是开区间
1.1.1 比如int left = 0
那就是闭区间, 因为最开始的这个数是一定得取得到的所以左边一定是闭区间
1.1.2 那么开区间是对象是右边,如果int right = nums.size() - 1
那就是闭区间,因为数组是从零开始,比如数组有10个元素,那下标范围就是0~9, 所以右指针为数组大小-1代表的是最后一个元素,那就是取得到最后一个元素,那就是闭区间。由于闭区间是取得到的元素,因此闭区间所指的值是参与运算的。
1.1.3 那么开区间呢,开区间就是取不到,比如int right = nums.size()
这实际上已经超出了数组的范围了,所以这是取不到的值,并且开区间所指的值是不参与运算的。 -
开闭区间的while判断条件
上面说的闭区间所指的值参与运算,开区间所指的值不参与运算在哪里发挥作用呢?
2.2.1 闭区间是while(left<=right)
请思考一下闭区间的最小区间是什么样的,此时的middle等于什么,循环的结束条件是什么?
可以来思考非常极端的例子,就是left和right靠的非常近甚至相等,闭区间的定义是[left, right]
这里的left是可以等于right的,当left=right那就区间已经缩小到只有一个元素的程度了,此时middle=left=right,此时还满足while(left<=right)
还要再进行一次运算并且是最后一次,这最后一次运算结果当middle是期望值那就中,不是的话就会要么left跑到right右边去或者right跑到left的左边来,循环while(left<=right)
结束。这个非常极端的区间只有一个值例子事实上middle=left=right的时候已经用了left和right的值了,所以right更新要middle-1,left要更新middle+1。
2.2.2 开区间while(left<right)
请思考一下此时最小区间应该是怎样的,此时的middle应该等于什么,此时的循环结束条件是什么?
由于是开区间[left, right)
所以left是不等于right的,由于int middle = left + ((right - left) / 2)
是向下取整的,所以最后一次运算的middle=left,此时如果middle是期望值就中,不是的话此时要么middle赋值给right,此时left=right导致循环while(left<right)
结束,要么左边闭区间left + 1导致left=right导致循环while(left<right)
结束。因为最后用到的是left等于middle的值,这个非常极端小的区间至少有两个值,准确地说是左边的值取得到右边的值取不到,因此左边更新的时候应该middle+1(毕竟左边取得到的时候已经使用过了),右边更新的时候应该right=middle(因为没有使用到但是还是要保留这个值作为开区间的边界)
以上是大白话的说明,接下来是代码演示
代码演示
1. 区间定义
1.1左闭右闭
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
1.2 左闭右开
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
2.循环条件和区间赋值
2.1 左闭右闭
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
2.2 左闭右开
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}