1 二分查找的基本原理
二分查找的使用情况:对于已经有序的数据来说,二分查找可以极大的提高查找效率,我们知道查找的本质就是排除,为什么二分查找可以右极高的查找效率呢?本质就是一次查找就可以排除一半无效的数据,这就是它高效的原因之一; 从时间复杂度度的角度来说:二分查找的查找效率达到 O(log n) ,这比遍历一遍数据的查找方式的时间复杂度O(n) 好很多;特别是体现在数据量极大的情况下;
二分查找的原理也就是:要查找某一个数x,前提查找的数据是有序的,那么就可以找到该有序数据的中间位置middle,用x和middle比较,如果x比middle大,那么说明,x在middle的右边,那么就排除middle左边的所有数据(包括middle本身);如果x比middle小,那么说明,x在middle的左边,那么就可以排除middle右边的所有数据(包括middle本身);如果x == middle等于那么就说明找到了x;
2 二分查找算法的两种写法
大家对二分擦或者算法总有一种一看就会一写就废的感觉; 主要是对查找的边界控制得不够清晰; 对于边界的控制有左闭右闭的写法,也有左闭右开的写法; 两种写法都是有些微的差别,但是原理是一样的;
2.1 左闭右闭的二分查找写法
void binary_search(vector<int> nums,int x)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int middle = left + ((right - left) >>1);
if (nums[middle] > x) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
}
}
}
}
我们重点关注循环的边界条件:while(left<=right) ,为什么要用<= ,而不用< ,其实就是因为我们用的是左闭右闭区间的写法,此时让left==right 时候,还在区间范围内,所以要继续查找;
2.2 左闭右开的区间的写法
void binary_search(vector<int> nums,int x)
{
int left = 0;
int right = nums.size();
while (left < right) {
int middle = left + ((right - left) >>1);
if (nums[middle] > x) {
right = middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
}
}
}
}
而我们的左闭右开区间的写法:相比于左闭右闭区间的写法,在while的判断条件有所不同,还有当 x < 中间值时候,right = middle,而不是right = middle-1;因为开区间,说明middle本身就不是在数据方位内的值了,所以不用-1;
3 四道经典力扣的二分算法的题目
3.1 有效的完全平方数
思路:只要我们在[1,num]的范围内,试数,找到一个数x ,只要满足x*x = num,那么就可以了; 此时,由于【1,num】的数都是升序,即有序,我们可以使用二分查找算法,这样查找效率更高;
代码:
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 1;
int right =num;
while(left <=right)
{
long long middle = left +((right - left)>>1);
long long val = middle*middle;
if(val == num)
return true;
else if(val < num)
left = middle+1;
else
right = middle -1;
}
return false;
}
};
3.2 x 的平方根
思路: 依旧是,在[0,x]中找到一个数,k,只要满足kk <= x, 那么就可以说明当kk = x,k就是x的算术平方根; 当k*k<x,那么说明k可能是x的算术平方根(题目的要求小数点的数会被舍掉,所以说,k * k< x是有可能成为x的算术平方根的值); 当k > x那么直接pass掉; 由于这个又是在[0,x]升序查找,所以说:我们还是用二分查找算法解决;
代码
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int ret = -1;
while(left <=right)
{
int middle = left + ((right-left)>>1);
if((long long)middle*middle<=x)
{
ret = middle;
left = middle+1;
}
else
{
right = middle -1;
}
}
return ret;
}
};
3.3 搜索插入位置
有序的数据,毫无疑问,直接二分算法:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right)
{
int middle = left +((right - left)>>1);
if(target<nums[middle])
{
right = middle;
}
else if ( target > nums[middle])
{
left = middle +1;
}
else
{
return middle;
}
}
return right;
}
};
3.4 在排序数组中查找元素的第一个和最后一个位置
二分算法不多说: 主要是我们要如何找到第一个数位置,和最后一个数位置: 首先我们得清楚,当我们找第一个数位置时候,需要把一种情况:即二分查找时候,target == nums[middle]时候得条件,也需要让 right = middle -1;的操作,这样才可以把最后一个数的位置给避开; 反之亦然,也就是说,擦或者最后一个数时候,也是这样操作;
代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = LeftIndex(nums,target);
int right = RightIndex(nums,target);
if(right < left )
return vector<int>{-1,-1};
return vector<int>{left,right};
}
int LeftIndex (vector<int>&nums ,int target)
{
int left = 0;
int right = nums.size() - 1;
while(left <= right)
{
int middle = left + ((right - left) >> 1);
if(target <= nums[middle])
right = middle - 1;
else
left = middle + 1;
}
return left;
}
int RightIndex (vector<int>&nums,int target)
{
int left = 0;
int right = nums.size() - 1;
while(left <= right)
{
int middle = left +( (right - left) >> 1);
if(target >=nums[middle])
left = middle + 1;
else
right = middle - 1;
}
return right;
}
};
|