算法笔记之二分法
二分法是一种在每一次比较之后将查询空间一分为二的算法。
时间复杂度为O(log(N)),空间复杂度为O(1)
leetcode例题
模板一 基础二分查找
1.给定一个数组nums,和目标查找的数字target,请编写一个函数返回目标数字的小标,如果没有则返回-1。
int search(int *nums,int numsSize,int target)
{
int left=0,right=numsSize-1,mid;
while(left<=right)
{
mid=(right-left)/2+left;
if(target<nums[mid])
right=mid-1;
else if(target>nums[mid])
left=mid+1;
else
return mid;
}
return -1;
}
2.给定一个数组nums,和目标查找的数组target,请编写一个函数返回目标数字的最小下标,如果没有则返回它应该被顺序插入的位置的下标。
int searchInsert(int* nums, int numsSize, int target){
int left=0,right=numsSize-1,ans=numsSize,mid;
while(left<=right)
{
mid=(right-left)/2+left;
if(target<=nums[mid])
{
ans=mid;
right=mid-1;
}
else
left=mid+1;
}
return ans;
}
3.给定一个整数不调用任何数学函数计算其算数平方根
int mySqrt(int x){
if(x==1)
return 1;
int left=0,right=x,ans=-1;
while((right-left)>1)
{
int mid=(right+left)/2;
if(x/mid<mid)//不用x<(mid*mid),是防止越界
right=mid;
else
left=mid;
}
return left;
}
4.整数数组是升序的,但在传递给函数之前从某一位置进行了旋转,例如数组[1,2,3,6,7,8]在下标为3处发生旋转成为[6,7,8,1,2,3],给定一个数组和一个目标整数target,请你返回目标整数的下标,否则返回-1。
//暴力解法 时间复杂度为O(n)
int search(int* nums, int numsSize, int target){
int t=-1;
for(int i=0;i<numsSize;i++)
{
if(nums[i]==target)
return i;
}
return t;
}
//用二分法 时间复杂度为O(logN)注意讨论特殊情况:数组为空或者为1
search(int *nums,int numsSize,int target)
{
if (!numsSize)
return -1;
if (numsSize== 1)
return nums[0] == target ? 0 : -1;
int left=0, right=numsSize-1;
while (left<=right)
{
int mid=(left+right)/2;
if(nums[mid]==target)
return mid;
if(nums[0]<=nums[mid])
{
if(nums[0]<=target&&target<nums[mid])
right=mid-1;
else
left=mid+1;
}
else
{
if(nums[mid]<target&&target<=nums[numsSize-1])
left=mid+1;
else
right=mid-1;
}
}
return -1;
}
5.猜数字大小
int guessNumber(int n){
int left=1,right=n;
while(left<right)
{
int mid=(right-left)/2+left;
if(guess(mid)<=0)
right=mid;
else
left=mid+1;
}
return right;
}
模板二 二分查找的高级模板,需要依赖当前元素与右元素的关系。
1.你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
int firstBadVersion(int n)
{
int left=1,right=n;
while(left<right)
{
int mid=left+(right-left)/2;
if(isBadVersion(mid))
{
right=mid;
}
else
left=mid+1;
}
return left;
}
2.寻找旋转排序数组中的最小值,例如nums[1,2,3,4,5,6]经过旋转变成nums[3,4,5,6,1,2],需要查找并返回1。保证查找空间在每一步至少有2个元素
int findMin(int* nums, int numsSize)
{
int left=0,right=numsSize-1;
while(left<right)
{
int mid=(right-left)/2+left;
if(nums[mid]>nums[right])
left=mid+1;
else
{
right=mid;
}
}
return nums[left];
}
模板三查找当前元素与左右元素的关系,保证当前查找空间至少有3个元素
1.给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
int binarySearch(int* nums, int numsSize, int target, bool lower) {
int left = 0, right = numsSize - 1, ans = numsSize;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
int leftIdx = binarySearch(nums, numsSize, target, true);
int rightIdx = binarySearch(nums, numsSize, target, false) - 1;
int* ret = malloc(sizeof(int) * 2);
*returnSize = 2;
if (leftIdx <= rightIdx && rightIdx < numsSize && nums[leftIdx] == target && nums[rightIdx] == target) {
ret[0] = leftIdx, ret[1] = rightIdx;
return ret;
}
ret[0] = -1, ret[1] = -1;
return ret;
}