- 要求数组是有序的;
- 通过区间划分数组,通过二分划分区间,根据查询条件构建新的查询区间,直到区间的长度为 0;
- 退出条件:区间长度为 0;
- 在这一过程中,查询区间应当只包含未确定的元素。
如何处理闭区间、开区间和半开半闭区间,以及大于、小于、大于等于、小于等于的情况。
以大于等于(找到数组中第一个大于等于 target 的元素位置)为例,分别展示如何处理闭区间、开区间和半开半闭区间。
闭区间
// 0 和 len - 1 都可以在数组上取到值, // 所以闭区间 [left, right]left = 0right = len - 1// 闭区间上存在 left == right 的情况while left <= rightmid = (left + right) / 2if nums[mid] < targetleft = mid + 1elseright = mid - 1endendreturn left
注意:left - 1 一定是小于 target 的数,right + 1 一定是大于等于 target 的数。
Q1: 为什么需要 left = mid + 1, right = mid - 1?
A1: 通过对比 nums[mid] 和 target,已经对 nums[mid] 做过了查询,所以新的查询区间中不应当包含 mid。
而在本例中,查询区间是闭区间,所以新的查询区间应当是 [mid + 1, right] 或者 [left, mid - 1] 才不包含 mid。
Q2: 为什么退出条件是 left <= right
A2: 当满足 left <= right 时,[left, right] 的长度不为 0,无法退出循环
开区间
// -1 和 len 都无法在数组上取到值, // 所以开区间 (left, right)left = -1right = lenwhile left + 1 < rightmid = (left + right) / 2if nums[mid] < targetleft = midelseright = midendendreturn left
和闭区间写法相比,主要有以下不同点:
- 初始值的选取,(left, right) 是一个开区间,所以应当选取 (-1, len),这样才能包含从 nums[0] 到 nums[len-1] 的所有元素。
- 循环的退出条件,left + 1 < right。当且仅当 (left, right) 的区间长度为 0 时,就退出循环。
- left = mid, right = mid。在开区间写法中,假设 left = mid,那么在新区间 (left, right) 等价于 (mid, right),排除掉了已经确定的元素 nums[mid]。
此外,还有半开半闭区间,在此不做赘述。