二分查找
二分查找作为程序员的一项基本技能,是面试官最常使用来考察程序员基本素质的算法之一,也是解决很多查找类题目的常用方法,它可以达到O(log n)的时间复杂度。
一般而言,当一个题目出现以下特性时,你就应该立即联想到它可能需要使用二分查找:
待查找的数组有序或者部分有序
要求时间复杂度低于O(n),或者直接要求时间复杂度为O(log n)
二分查找有很多种变体,使用时需要注意查找条件,判断条件和左右边界的更新方式,三者配合不好就很容易出现死循环或者遗漏区域,本篇中我们将介绍常见的几种查找方式的模板代码,包括:
- 标准的二分查找
- 二分查找左边界
- 二分查找右边界
- 二分查找左右边界
- 二分查找极值点
本文的内容来自于笔者个人的总结,事实上二分查找有很多种等价的写法,本文只是列出了笔者认为的最容易理解和记忆的方法。
例1(LeetCode.704 二分查找)
题面:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1 | 描述 |
---|---|
输入 | nums = [-1,0,3,5,9,12], target = 9 |
输出 | 4 |
说明 | 9 出现在 nums 中并且下标为 4 |
思路:经典的二分查找
二分查找的做法是,定义查找的范围[left,right]初始查找范围是整个数组。每次取查找范围的中点 mid,比较num[mid]与target的大小,若相等则返回,不相等可以根据关系缩小一半的查找范围
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right)
{
int mid = (right-left)/2+left;
int num=nums[mid];
if(num==target)return mid;
else if (num<target)left=mid+1;
else if (num>target)right=mid-1;
}
return -1;
}
};
分析二分查找的一个技巧是:不要出现else
,而是把所有情况用else if
写清楚,这样可以清楚地展现所有细节些。
例2(LeetCode.278 第一个错误的版本)
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例1 | 描述 |
---|---|
输入 | n = 5,bad=4 |
输出 | 4 |
说明 | 调用 isBadVersion(3) -> false 调用 isBadVersion(4) -> true,所以,4 是第一个错误的版本。 |
思路:整体上使用二分查找,但是需要注意一个细节:如果mid
为false
,那么第一个错误可能为自己。但如果mid
为true
,则第一个错误一定在[mid+1,right]
ps:不要把isBadVersion当成测试是否成功
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int left=1;
int right=n;
while(left<right)
{
int mid = (right - left) /2 + left;
if(isBadVersion(mid))right=mid;
else left=mid+1;
}
return left;
}
};
例3(LeetCode.35 搜索插入位置)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为O(log n)
的算法。
示例1 | 描述 |
---|---|
输入 | nums = [1,3,5,6], target = 5 |
输出 | 2 |
这一部分一定要分析清楚:
- 题目要找的元素是:第一个大于等于 target 的元素的下标。
- 数组的长度 len 也有可能是问题的答案。
上面 2 个小点,都需要仔细分析题意和几个示例得到,任何模板都不能回答这样的问题。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<target)
l=mid+1;
else r=mid-1;
}
return l;
}
};