算法解释
二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取
一部分继续查找,将查找的复杂度大大减少。对于一个长度为 O(n) 的数组,二分查找的时间复
杂度为 O(logn)。
举例来说,给定一个排好序的数组 {3,4,5,6,7},我们希望查找 4 在不在这个数组内。第一次
折半时考虑中位数 5,因为 5 大于 4, 所以如果 4 存在于这个数组,那么其必定存在于 5 左边这一
半。于是我们的查找区间变成了 {3,4,5}。(注意,根据具体情况和您的刷题习惯,这里的 5 可以
保留也可以不保留,并不影响时间复杂度的级别。)第二次折半时考虑新的中位数 4,正好是我们
需要查找的数字。于是我们发现,对于一个长度为 5 的数组,我们只进行了 2 次查找。如果是遍
历数组,最坏的情况则需要查找 5 次。
具体到代码上,二分查找时区间的左右端取开区间还是闭区间在绝大多数时候都可以,因此
会容易搞不清楚如何定义区间开闭性。这里提供两个小诀窍:
- 第一是尝试熟练使用一种写法,比如左闭右开(满足 C++、Python 等语言的习惯)或左闭右闭(便于处理边界条件),尽量只保持这一种写法;
-第二是在刷题时思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某种写法无法跳出死循环,则考虑尝试另一种写法。下面有一个例子后面专门写了一个标题来讨论这个问题。
二分查找也可以看作双指针的一种特殊情况,但我们一般会将二者区分。双指针类型的题,
指针通常是一步一步移动的,而在二分查找里,指针每次移动半个区间长度。
例1:69 x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
分析:
这题让我实现sqrt这个函数,可以把这道题想象成,给定一个非负整数 a,求 f(x) = x 2 − a = 0 的解。因为我们只考虑 x ≥ 0,所以 f(x) 在定义域上是单调递增的。考虑到 f(0) = −a ≤ 0,f(a) = a 2 − a ≥ 0,我们
可以对 [0,a] 区间使用二分法找到 f(x) = 0 的解。
注意:
- 这一题的问题在于,我们要求的解和真实的平方根是不一样的,比如例2 ,8开根号应该是2.xxx,但是我们要返回2,而2的平方==4,而不等于8。
- 题目给出的数字比较大的话,midmid可能溢出了int型,所以我们在前面加个(long)midmid
class Solution {
public:
int mySqrt(int x) {
if(x==0) return 0;
int l=0,r=x,mid;
while(l<=r)
{
mid=(l+r)/2;
if((long) mid*mid==x) return mid;
else if((long) mid*mid>x) r=mid-1;
else l=mid+1;
}
return r;
}
};
34 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置.
如果数组中不存在目标值 target,返回 [-1, -1]。
设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
分析:
首先以下情况应该返回[-1,-1]
-
target比nums[nums.size()-1]大
-
target比nums[0]小
-
nums.size()==0
-
nums里没找到target
返回两个-1的情况是很多的,初步的设想,找出正常返回两个数值的,其他全部返回-1.
整理一下返回-1的情况应该是这样的,只有两种: -
如果是空的
-
或者连第一个都没找到
写两个函数,一个用来找第一个,一个用来找最后一个,找第一个的如果没有找到就返回-1
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
if (nums.empty()) return vector<int>{
-1, -1};
int lower=findfirst(nums,target);
int upper=findlast(nums,target);
if(nums[lower]!=target) return vector<int>{
-1,-1};
return vector<int>{
lower,upper};
}
int findfirst(vector<int>& nums,int target)
{
int l=0,r=nums.size()-1,mid;
while(l<r)
{
mid=(l+r)/2;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
return l;
}
int findlast(vector<int>& nums,int target)
{
int l=0,r=nums.size(),mid;
while(l<r)
{
mid=(l+r)/2;
if(nums[mid]>target) r=mid;
else l=mid