目录
-
二分法的原理
二分查找,又叫做折半查找,是一种在有序数组中查找某一特定元素的搜索算法。
1.二分查找(模板1)
这是我们经常见到的二分查找算法,主要作用是在循环体中查找某一元素,主要存在三个分支,其中两个分支用于边界的搜索。
int Binary_search(int arr[],int n,int target)
{
//在[l,r]闭区间搜索target
int l=0,r=n-1;
while(l<=r)//当l==r时,区间依然有效
{
int mid=l+(r-l)/2;
if(arr[mid]==target)
return mid+1;
else if(arr[mid]<target)
l=mid+1;//在[mid+1,r]闭区间搜索target
else
r=mid-1;//在[l,mid-1]闭区间搜索target
}
return -1;
}
2.二分查找(模板2)
这种二分查找,主要是在循环体内缩小搜索区域,当退出循环以后区间只剩下一个元素,视情况单独判断。
int search(int nums[],int left,int right,int target)
{
//在[left,right]里查找target
while(left<right)
{
//选取中位数时下取整
int mid=left+(right-left)/2;
if(check(mid))
{
//下轮搜索区间[mid+1,rihjt]
left=mid+1;
}
else
{
//下轮搜索区间[left,mid]
right=mid;
}
}
//视情况而定,判断left或者right是否符合题意。
}
int search(int nums[],int left,int right,int target)
{
//在[left,right]里查找target
while(left<right)
{
//选取中位数时上取整
int mid=left+(right-left+1)/2;
if(check(mid))
{
//下轮搜索区间[left,mid-1]
right=mid-1;
}
else
{
//下轮搜索区间[mid,right]
left=mid;
}
}
//视情况而定,判断left或者right是否符合题意。
}
-
例题
1.x的平方根(leetcode 69)
- 题目描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
- 分析:
首先,我们知道一个非负整数 x 的平方根在[ 0 ,x]之间,而且这个区间是有序的。那么我们就可以使用二分查找法。
- 如果中间元素 mid 等于x的平方根,那么直接返回。
- 如果x/mid的值大于mid,说明x的平方根在mid的右边,则l=mid+1;
- 如果x/mid的值小于mid,说明x的平方根在mid的左边,则r=mid-1;
- 如果没找到合适的值,则返回向下取整的值min(l,r),其实就是r,因为当不满足l<=r时,r<l;
class Solution {
public:
int x_sqrt(int x)
{
if(x<2)
return x;
int l=2;
int r=x/2;
while(l<=r)
{
int mid=l+(r-l)/2;
if(x/mid==mid)
return mid;
else if(x/mid>mid)
l=mid+1;
else
r=mid-1;
}
return r;
}
int mySqrt(int x) {
return x_sqrt(x);
}
};
2.Pow(x,n)(leetcode 50)
- 题目描述:
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
- 分析:
这道题计算x的平方,我们可以将x进行分组,分成一等分,一等分的,然后在将每一等分合并起来,组成最后的结果,这里有点类似于归并排序的过程。
double powN(double x,long long l,long long r)
{
if(l>=r)
return x;
long long mid=l+(r-l)/2;
double left=powN(x,l,mid);
double right=powN(x,mid+1,r);
return left*right;
}
注:这是我自己的实现,但是在最后会超时。
double powHelper(double x,long long N)
{
if(N==0)
return 1.0;
double y=powHelper(x,N/2);
return N%2==0?y*y:y*y*x;
}
class Solution {
public:
double powN(double x,long long l,long long r)
{
if(l>=r)
return x;
long long mid=l+(r-l)/2;
double left=powN(x,l,mid);
double right=powN(x,mid+1,r);
return left*right;
}
double powHelper(double x,long long N)
{
if(N==0)
return 1.0;
double y=powHelper(x,N/2);
return N%2==0?y*y:y*y*x;
}
double myPow(double x, int n) {
long long N=n;
if(N==0)
return 1;
else if(N<0)
// return 1.0/powN(x,1,N*-1);
return 1.0/powHelper(x,N*-1);
else
// return powN(x,1,N);
return powHelper(x,N);
}
};
3.山脉数组的峰顶索引(leetcode 852)
- 题目描述:
我们把符合下列属性的数组 A 称作山脉:
A.length >= 3,存在 0 < i < A.length - 1 使得A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1]
给定一个确定为山脉的数组,返回任何满足 A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 的 i 的值。
示例 1:
输入:[0,1,0]
输出:1
示例 2:
输入:[0,2,1,0]
输出:1
提示:
3 <= A.length <= 10000
0 <= A[i] <= 10^6
A 是如上定义的山脉
- 分析:
我们需要找到山峰,可以通过线性查找,当A[i]>A[i+1]时,结束遍历,这样的时间复杂度为O(n),为了能够降低时间复杂度,我们可以使用二分查找。
- 当A[mid]<A[mid+1]时,处于山峰的左边,索引 mid 一定不是峰顶的位置,搜索峰顶的位置在 mid 的右边(不包括i);
- 当A[mid]>A[mid+1]时,处于山峰的右边,索引 mid 可能是峰顶的位置,搜索峰顶的位置在 mid 的左边(包括i);
class Solution {
public:
int peakIndexInMountainArray(vector<int>& A) {
int l=0;
int r=A.size()-1;
while(l<r)
{
int mid=l+(r-l)/2;
if(A[mid]>A[mid+1])
r=mid;
else
l=mid+1;
}
return l;
}
};
4.山脉数组中查找目标值(leetcode 1095)
- 题目描述
(这是一个 交互式问题 )
给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。
如果不存在这样的下标 index,就请返回 -1。
何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:
首先,A.length >= 3
其次,在 0 < i < A.length - 1 条件下,存在 i 使得:
A[0] < A[1] < ... A[i-1] < A[i]
A[i] > A[i+1] > ... > A[A.length - 1]
你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:
MountainArray.get(k) - 会返回数组中索引为k 的元素(下标从 0 开始)
MountainArray.length() - 会返回该数组的长度
注意:
对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。此外,任何试图规避判题系统的解决方案都将会导致比赛资格被取消。
为了帮助大家更好地理解交互式问题,我们准备了一个样例 “答案”,请注意这 不是一个正确答案。
示例 1:
输入:array = [1,2,3,4,5,3,1], target = 3
输出:2
解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。
示例 2:
输入:array = [0,1,2,4,2,1], target = 3
输出:-1
解释:3 在数组中没有出现,返回 -1。
提示:
3 <= mountain_arr.length() <= 10000
0 <= target <= 10^9
0 <= mountain_arr.get(index) <= 10^9
- 分析:
这道题有几个重要的信息:
- 山脉数组是两个有序数组的组成的数组,存在一个山峰值。
- 对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。
- 数据规模:3 <= mountain_arr.length() <= 10000
从这些信息中,我们得知:我们对其峰值进行查找需要使用一次二分查找,将峰值前半部分的查找需要使用一次二分查找,然后峰值后半部分的查找需要使用一次二分查找,一共三次二分查找。
/**
* // This is the MountainArray's API interface.
* // You should not implement it, or speculate about its implementation
* class MountainArray {
* public:
* int get(int index);
* int length();
* };
*/
class Solution {
public:
int binnary_search(MountainArray &mountainArr,int start,int end,int target,bool flag)
{
int l=start;
int r=end;
while(l<=r)
{
int mid=l+(r-l)/2;
if(mountainArr.get(mid)==target)
return mid;
if(flag)
{
if(mountainArr.get(mid)>target)
{
r=mid-1;
}
else
l=mid+1;
}
else
{
if(mountainArr.get(mid)<target)
{
r=mid-1;
}
else
l=mid+1;
}
}
return -1;
}
int findInMountainArray(int target, MountainArray &mountainArr) {
int length=mountainArr.length();
if(length<3)
return -1;
int l=0;
int r=length-1;
while(l<r)
{
int mid=l+(r-l)/2;
if(mountainArr.get(mid)>mountainArr.get(mid+1))
r=mid;
else
l=mid+1;
}
int prior=binnary_search(mountainArr,0,l,target,true);
if(prior!=-1)
return prior;
int back=binnary_search(mountainArr,l+1,length-1,target,false);
return back;
}
};
5.有序数组中的单一元素 (leetcode 540)
- 题目描述:
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。
- 分析:
从注意事项中,可以得知这道题算法时间复杂度需要为 O(log n),那么就会想到用二分查找.
- 首先我们需要知道,数组中的元素数量为奇数,元素相对有序。
- 当mid=mid+1时,如果mid右边的元素个数为偶数,说明mid右边含有偶数个数,那么去掉mid+1后,就为奇数个,说明单一元素在mid+1的右边(l=mid+2)。
- 当mid=mid+1时,如果mid右边的元素个数为奇数,说明mid右边含有奇数个数,那么去掉mid+1后,就为偶数个,说明单一元素在mid的左边(r=mid-1)。
- 当mid=mid-1时,如果mid右边的元素个数为偶数,说明mid右边含有偶数个数,那么说明单一元素在mid-1的左边(r=mid-2)。
- 当mid=mid-1时,如果mid右边的元素个数为奇数,说明mid右边含有奇数个数,那么说明单一元素在mid的右边(l=mid+1)。
- 上述情况都不是,就直接返回该值。
class Solution {
public:
int binary_search(vector<int>&nums)
{
int l=0;
int r=nums.size()-1;
while(l<r)
{
int mid=l+(r-l)/2;
bool flag=(r-mid)%2==0;
//中间元素和相邻右边的元素相等
// cout<<"mid: "<<mid<<" l: "<<l<<" r: "<<r<<endl;
if(nums[mid]==nums[mid+1])
{
//右半部分数量为偶数
if(flag)
l=mid+2;
else
r=mid-1;
}
else if(nums[mid]==nums[mid-1])
{
//右半部分数量为偶数
if(flag)
r=mid-2;
else
l=mid+1;
}
else
return nums[mid];
}
return nums[l];
}
int singleNonDuplicate(vector<int>& nums) {
// if(nums.empty())
// return 0;
// int a=0;
// for(auto num:nums)
// a^=num;
// return a;
int a=binary_search(nums);
return a;
}
};