hello, 大家好, 我是帕鲁, 今天带大家彻底领悟二分查找
Just practice No Skill
1. 何为二分查找
二分查找, 又为折半查找, 通过比较中间值, 使每一次比较都使搜索范围缩小一半.
时间复杂度为 O(Logn)
最坏情况下时间复杂度为 O(Logn)
最好情况下时间复杂度为 O(1) 直接就找到了
在给定的一串有序(默认升序)数组 nums 中, 给定一个 target , 如果值存在, 则返回下标值, 如果不存在, 返回 -1
步骤:
- 在有序数组中值 查找, 如果找到, 则直接返回
- , 则扔掉 , 反之扔掉 部分
- 在剩下的数组范围 执行 步骤1
如果上述循环穿透了整个数组范围, 仍没有返回, 说明没找到, return -1
2. 举例说明
在 中找到 的位置
步骤:
已知:
- 找到的中值 , 判断 , 不等
- 4 比 7小, 所以要在7的左半边查找
- 新的数组为 , 同时更新 mid = 4, 返回步骤1
- 发现找到了,
因为整个数组是有序的, 目标值比 小, 那么我们只要在 左半边内找即可, 这就是为什么又被称为折半查找, 每次在一半内找, 然后舍弃另一半
3. 代码模版
left: 左边界 right: 右边界 此处和下文均代表index
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > target:
right = mid - 1 // 更新右边界
elif nums[mid] < target:
left = mid + 1 // 更新左边界
else:
return mid
return -1
4. 彻底领悟 ( 重点!)
这里有3个关键: 悟了之后闭着眼也能写出来
- 是 还是 ?
- 条件是 还是 呢 ?
- 更新右边界的时候, 到底是 还是
首先思考一个问题: while 是什么意思
有效范围内, 就应该一直循环整个数组, 且不遗漏任何元素 , 换句话说, 条件一旦不满足, 就要跳出循环
情况一: 左闭右闭
假设 , 那么 while left ? right 这里到底是 还是 呢
解析:
这里 很明显指向数组最后一个元素
那么假设 的情况
最后一个元素会被遍历到吗, 很明显, 不会, 因为都等不到 的时候, 循环就退出了
该不该被遍历到, 应该 , 合法, 所以应该
在满足 的情况下, 存在元素没有被遍历到, 那么最后结果肯定是不对的的.
如果是 的情况, 元素应该被遍历到 但实际上没有被遍历到, < 错误
再次强调 因为 合法 且范围有效, 那么 如果写成 , 那么就会遗漏 nums[right] 的值
所以:
每次更新 边界的时候, 如果是 写成 , 那么下次 循环 就会被计算进去, 但实际上 在上一次循环就已经被计算过了 ( 是闭区间), 不应该被重复计算, 所以应该是 前面一位
首先要保证数组每个元素都被遍历到
其次 代表 指向数组最后一位
那么自然数组最后一个元素是需要被访问到的
所以是
如果仅仅是 , 那么 所指向的元素永远都不会被访问到
可能有点绕, 但希望读者能细细品味
如果稍微对上面说的有点理解, 那么我们来看还有一种情况:
情况二: 左闭右开
假设 , 那么 while left ? right 这里到底是 还是 呢
注意: 和情况一不一样的是, 这里的 right 并不是指向数组最后一位元素
解析:
那么再来假设 的情况
最后一个元素会被遍历到吗, 很明显, 会, 因为 才指向数组最后一个元素, 而 在 的有效范围内
该不该被遍历到, 不应该 , 不合法, 所以不应该
显然不合法
在满足 的情况下, 元素都被遍历到了, pass
如果是 的情况, 不合法, <= 错误
所以:
每次更新 边界的时候, 注意这里 right 是开区间, 那么 就显得非常合理, 在上一次 就已经计算过, 此次循环不会再次计算, 而是计算
思考: 如果这里写成 , 会是什么情况?
如果写成 , 由于 是开区间, 所以 下标 所在的值永远不会被访问到, 存在遗漏
5. 一些细节
- 在计算中位数 的时候, 可能会存在整数溢出, 故采用
mid = left + (right - left) // 2
- 更新左边界: 把 指向的位置从 开始, 自然就扔掉了左边一半原数组
- 更新右边界: 把 指向的位置从 或者( )开始, 自然就扔掉了右边一半原数组
6. 总结和回顾
- 如何 定义 的指向位置
- 的有效范围内, 下标是否有效, 是否会被计算
- 更新 边界时, 由 right 定义 决定 (开区间) 还是 (闭区间)