- 二分查找详解
点击这里查看力扣题目,了解详细信息。
当你面对一个升序排列的整数数组 nums
并寻找一个特定目标值 target
时,你可以采用一种高效的搜索方法来定位这个目标值。如果目标值存在于数组中,那么就返回它的索引;如果不存在,就返回 -1 作为标记。
示例 1:
makefile
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
说明: 目标值 9 在数组中的索引为 4
示例 2:
makefile
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
说明: 目标值 2 并不在数组中,因此返回 -1
提示:
- 数组
nums
中的元素是唯一的。 - 数组的大小
n
范围在[1, 10000]
。 - 数组元素的值域是
[-9999, 9999]
。
方法探究
此题假定数组是有序且无重复元素的,这为二分查找提供了理想的应用条件。但是,要正确实现二分查找,关键在于如何处理搜索的边界条件,这往往是编码时最容易出错的地方。主要有两种方式来定义查找区间:左闭右闭 [left, right]
和左闭右开 [left, right)
。
二分查找的实现方法
-
采用左闭右闭区间
[left, right]
:- 循环条件使用
while (left <= right)
,意味着left
和right
相等是有效的,需要被检查。 - 如果发现
nums[middle] > target
,则说明target
不可能是nums[middle]
,下一步right
应该调整到middle - 1
。
- 循环条件使用
rust
impl Solution {
pub fn search(nums: Vec<i32>, target: i32) -> i32 {
let (mut left, mut right) = (0_i32, nums.len() as i32 - 1);
while left <= right {
let mid = (left + right) / 2;
match nums[mid as usize].cmp(&target) {
Ordering::Less => left = mid + 1,
Ordering::Greater => right = mid - 1,
Ordering::Equal => return mid,
}
}
-1
}
}
-
采用左闭右开区间
[left, right)
:-
循环条件改为
while (left < right)
,因为当left
和right
相等时,区间不再有效。 -
如果
nums[middle] > target
,那么right
更新为middle
,因为nums[middle]
已经被排除在外,接下来的搜索区间应该是左闭右开的[left, middle)
。
-
rust
use std::cmp::Ordering;
impl Solution {
pub fn search(nums: Vec<i32>, target: i32) -> i32 {
let (mut left, mut right) = (0_i32, nums.len() as i32);
while left < right {
let mid = (right + left) / 2;
match nums[mid as usize].cmp(&target) {
Ordering::Less => left = mid + 1,
Ordering::Greater => right = mid,
Ordering::Equal => return mid,
}
}
-1
}
};
- 时间复杂度: 对于两种方法而言,都是 O(log n),因为每次查找都是将查找区间缩小到原来的一半。
- 空间复杂度: O(1),我们只需要常数级别的额外空间。
深入理解二分查找
二分查找,作为一个经典而高效的搜索算法,之所以能够在有序数组中快速定位元素,是因为它每一步都将待搜索的区间减半,从而大幅减少了搜索所需的时间。但是,恰当地实现二分查找并非毫无挑战。很多时候,我们可能会遇到各种边界条件的处理问题,导致写出来的代码不符合要求,但是实际上,搞明白边界问题,二分查找就是手拿把掐,手到擒来!Pomelo_刘金,转载请注明原文链接。感谢!