二分查找算法的总结(含有各种题型)

普通的二分查找Ⅰ

[left,right]

int binarySearch1(vector<int> nums,int target){
    int left = 0;
    int right = nums.size() - 1;//这个right是可以取到的
    while(left <= right){//当left==right,区间[left, right]依然有效,所以用 <=
        int mid = left + (right - left) / 2;
    	if(target < nums[mid]){
            right = mid - 1;//下次寻找区间[left, mid - 1]
        }else if(target > nums[mid]){
            left = mid + 1;//下次寻找区间[mid - 1 , right]
        }else if(target == nums[mid]){
            return mid;
        }
    }
    return -1;
}

普通的二分查找Ⅱ

[left,right)

int binarySearch2(vector<int> nums,int target){
    int left = 0;
    int right = nums.size();//这个right是取不到的
    while(left < right){ //right 取不到
        int mid = left + (right - left)/2;
        if(target < nums[mid]){
            right = mid; //下次取不到 mid ,即下次的right 取不到
        }else if(target > nums[mid]){
            left = mid + 1;
        }else if(target == nums[mid]){
            return mid;
        }
    }
    return -1;
}

1、其实模板1和模板2本质上是根据代码来区分的,而不是应用场景。如果写完之后发现是l = mid,那么在计算mid时需要加上1,否则如果写完之后发现是r = mid,那么在计算mid时不能加1

2、下取整

左加右减,右边界加一

查找符合条件的左边界

本质上查找符合if条件的第一个数(从左向右边数)

闭区间

当将区间 [l, r] 划分成 [l, mid] 和 [mid + 1, r] 时,其更新操作是 r = mid 或 l = mid + 1,计算 mid 时不需要加1,即 mid = (l + r)/2。

C++代码模板:

#include <iostream>
#include <vector>
using namespace std;
/* 二分查找模板1:在数组nums中查找target,找到返回下标值,否则返回-1;
   注意,若nums中有多个一样的target值,这个模板返回的是第一次找到的target的下标位置;
   最后l一定是等于r的
*/
int binSearch1(vector<int> nums, int target) {
	int n = nums.size();
	// 定义双指针 
	int l = 0, r = n - 1;
	while (l < r)
	{
		// 二分查找,最后循环终止一定是l == r 
		int mid = l + r >> 1;
		if (nums[mid] >= target) //target在[l,mid]
		{
			r = mid;
		}
		else {
			l = mid + 1;//target在[mid +1.r]
		}
	}
	//上面的查找满足nums[mid] >= target的第一个数,加上下面这个判断就是查找第一个target
	if (nums[l] != target) {
		// 没找到则返回-1 
		//return -1;
	}
	return l;  // 找到返回下标 
}

int main() {
	vector<int> nums = { 1, 3,5,5, 7 };
	int ans = binSearch1(nums, 5);
	cout << ans;  // 5 
	return 0;
}


查找符合条件的右边界

本质上查找符合if条件的第一个数(从右向左边数)

闭区间

模板2
当将区间 [l, r] 划分成 [l, mid - 1] 和 [mid, r] 时,其更新操作是 r = mid - 1 或者 l = mid,此时为了防止死循环,计算 mid 时需要加1,即 mid = ( l + r + 1 ) /2

#include <iostream>
#include <vector>
using namespace std;
/* 二分查找模板2:在数组nums中查找target,找到返回下标值,否则返回-1; 
   注意,若nums中有多个一样的target值,这个模板返回的是最后一次找到的target的下标位置; 
*/
 
int binSearch2 (vector<int> nums, int target) {
	int n = nums.size();
	// 定义双指针
	int l = 0, r = n - 1;
	while (l < r) {
		// 二分查找,最后循环终止一定是l == r
		int mid = l + r + 1 >> 1;  // 此处为什么+1呢?l会卡住,防止陷入死循环,自己举个两个元素的例子试试就懂了,例如nums = {1, 3}, target = 3; 
		if (nums[mid] <= target) {
			l = mid;
		} else {
			r = mid - 1;
		}
	} 
	if (nums[l] != target) {
		// 没找到则返回-1 
		return -1;
	}
	return l;  // 找到返回下标 
}
 
int main() {
	vector<int> nums = {1, 3, 6, 8, 11, 13, 13, 13, 86, 100};
	int ans = binSearch2(nums, 13);
	cout << ans;  // 7 
	return 0;
}

举例各种题型

一、查找一个数

1、x的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

class Solution {
public:
    int mySqrt(int x) {
        if(x==0) return x ;

        int left = 1;
        int right = x ;
        while(left < right){
            int mid = left + (right - left) / 2;
            long mid_2 = (long) mid *mid;
            if(mid_2 >= x) //x的算术平方根掉落在[l,mid]
              right = mid;
            else
               left = mid + 1;


        }
        if((long)left* left != x)//在我们找不到整数的情况下,我们这个会取右边第一个比它大的整数
              return left -1;

  return left;
    }
};

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。

//升序
class Solution {
public:
    int guessNumber(int n) {
        int l = 1;
        int r = n;
       
        
        while(l<r){
            int mid = l + (r -l) /2 ;
         
           if (guess(mid) <= 0) //满足条件就的话,值就在[l,mid]
                r = mid;
            else 
              
              l = mid + 1;//不然区间就在[mid +!,r]
        }

        return l;
        
    }
    
};

3、搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

//在一个二断的区间找一个数
class Solution {
public:
    int search(vector<int>& nums, int target)
     {
         //先判断target在那一段区间

          
        int l = 0;
        int r = nums.size()-1;
        while(l < r){
            int mid =  l + (r -l)/2;
            if(nums[mid] <= nums[r])//右半段有序
            {
                if(nums[mid] < target && nums[r] >= target) //要找的在[l,mid]
                    l = mid +1 ;
                else 
                       r= mid;


            }
            else//target左半段有序
            {
                 if(nums[mid] >= target && nums[l] <= target)//要找的target在[mid+1,r]
                    r = mid;
                 else 
                       l = mid +1;
            }
              
            
        }
        

        return nums[l] == target ? l :-1;//找到了返回1,不然返回-1

    }
};

二、查找左侧边界

1、第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

class Solution {
public:
    int firstBadVersion(int n) {
        int left = 1, right = n;
        while (left < right) { // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (isBadVersion(mid)) {
                right = mid; // 答案在区间 [left, mid] 中
            } else {
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }
};

2、寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

//上坡必有顶

class Solution {
public:
    int findPeakElement(vector<int>& nums) 
    {
             int l = 0;
             int r = nums.size()-1;      
                while(l < r){
                    int mid = l + (r - l) / 2;
                    if(nums[mid] > nums[mid+1]) //顶点在[l,mid]
                             r = mid;
                    else
                          l = mid +1;
                
                    }
         return l;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值