首页 > 其他分享 >二分查找习题篇(上)

二分查找习题篇(上)

时间:2024-11-07 23:17:16浏览次数:5  
标签:二分 right target nums int mid 查找 习题 left

二分查找习题篇(上)

1.二分查找

题目描述:

给定⼀个 n 个元素==有序的(升序)==整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

​输入: nums = [-1,0,3,5,9,12], target = 9

​输出: 4

解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2

输出: -1

解释: 2 不存在 nums 中因此返回 -1

提示:

你可以假设 nums 中的所有元素是不重复的。

n 将在 [1, 10000]之间。

nums 的每个元素都将在 [-9999, 9999]之间。

解法一:暴力解法

从前往后枚举每一个元素,将其与目标值进行对比。

时间复杂度最差为O(N)。

解法二:二分查找算法

当数组具有“二段性”时,我们就可以用二分查找算法。

算法思路:

  1. 定义 left , right 指针,分别指向数组的左右区间。

  2. 当left<=right时,下列一直循环:

​ 找到待查找区间的中间点 mid ,找到之后分三种情况讨论:

  • arr[mid] == target:返回 mid 的值;
  • arr[mid] > target:让 right = mid - 1,在 [left, right] 的区间继续查找 ,重复 2 过程;
  • arr[mid] < target:让 left = mid + 1, 在 [left, right] 的区间继续查找,重复 2 过程;
  1. 当left>right时,说明整个区间都没有这个数,返回 -1 。

细节问题:

1.循环结束的条件

当left>right

2.为什么是正确的?

二分查找是从暴力解法优化而来的

3.时间复杂度

1次——>n/21=n/2

2次——>n/22=n/4

3次——>n/23=n/8

…次——>…

x次——>n/2x=1(当left==right,找到要找的元素时)

2x=n——>x=logN

因此,二分查找的时间复杂度是logN.

代码实现:

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left=0,right=nums.size()-1;
        while(left<=right)//每次查找的元素都是未知的,所以要取等号
        {
            int mid=left+(right-left)/2;  //防止溢出
            if(nums[mid]>target) right=mid-1;
            else if(nums[mid]<target) left=mid+1;
            else return mid;
        }
        return -1;
    }
};

朴素二分模版:

​ while(left<=right)//每次查找的元素都是未知的,所以要取等号
​ {
​ int mid=left+(right-left)/2; //防止溢出,替换成left+(right-left+1)也可以,只不过是偶数个元素时,mid有2个中间值,左右均可
​ if(……)

​ right=mid-1;
​ else if(……)

​ left=mid+1;
​ else

​ return ……;
​ }

2.在排序数组中查找元素的第一个和最后一个位置

题目描述:

给你一个按照非递减顺序排列(趋势要么递增要么不变)的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

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

示例 1:

​ 输入:nums = [5,7,7,8,8,10], target = 8
​ 输出:[3,4]

示例 2:

​ 输入:nums = [5,7,7,8,8,10], target = 6
​ 输出:[-1,-1]

示例 3:

​ 输入:nums = [], target = 0
​ 输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

解法一:暴力查找

从前往后枚举每一个元素,将其与目标值进行对比,相同时记为begin,继续向后寻找,直到找到该数的末尾位置记为end。返回begin,end即可。

时间复杂度最差为O(N)。

解法二:二分查找

==当数组具有“二段性”时,我们就可以用二分查找算法。==接下来我们来寻找该数组的“二段性”.

记左边界为Bleft,右边界为Bright.

1.查找区间的左端点

这里,我们把数组的元素分为两部分——小于target的部分[left,Bleft-1] and 大于等于target的部分[Bleft,right]。

  • 当mid在[left,Bleft-1]的区间,要找左区间,我们可以直接舍去[left,mid],更新left=mid+1;
  • 当mid在[Bleft,right]的区间,要找左区间,我们可以直接舍去[mid+1,right],更新right=mid(因为mid可能是最终结果);
  • 之后在[left,right]上继续寻找左边界。

细节处理:

1.循环条件:

left<right;

  • left=right时,就是最终结果,无需判断;如果判断,就会死循环。

2.求中点的操作:

  • 正确写法:left+(right-left)/2:求的是靠左的位置(向下调整)。

  • 找左端点时求中点要向下取整

    要是向上调整,判断之后,当mid在[Bleft,right]时,要更新right=mid。再进行下一次判断,要是又面对同样的情况,又更新right=mid……又循环往复……

2.查找区间右端点:

这里,我们把数组的元素分为两部分——小于等于target的部分[left,Bright] and 大于target的部分[Bright+1,right]。

  • 当mid在[left,Bright]的区间,要找右区间,我们可以直接舍去[left,mid-1],更新left=mid(因为mid可能是最终结果);
  • 当mid在[Bright+1,right]的区间,要找右区间,我们可以直接舍去[mid,right],更新right=mid-1;
  • 之后在[left,right]上继续寻找右边界。

细节处理:

1.循环条件:

left<right

2.求中点的操作:

  • 正确写法:left+(right-left+1)/2:求的是靠右的位置(向上调整);

  • 找右端点时求中点要向上取整

    要是向下调整,判断之后,当mid落在[left,Bright]时,要更新left=mid。再进行下一次判断,要是又面对同样的情况,又要更新left=mid……又循环往复……

代码实现:
class Solution
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        // 处理边界情况
        if(nums.size() == 0) 
            return {-1, -1};

        int begin = 0;
        // 1. 二分左端点
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) 
                left = mid + 1;
            else 
                right = mid;
        }
        
        // 判断是否有结果
        if(nums[left] != target) 
            return {-1, -1};
        else 
            begin = left; // 标记⼀下左端点
        
        // 2. 二分右端点
        left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target) 
                left = mid;
            else 
                right = mid - 1;
        }
        
        return {begin, right};
    }
};

查找区间左端点的模版:

while(left < right)
{
int mid = left + (right - left) / 2;
if(……) left = mid + 1;
else right = mid;
}

查找区间右端点的模版:

while(left < right)
{
int mid = left + (right - left + 1) / 2;
if(……) left = mid;
else right = mid - 1;
}

总结切记:分类讨论的代码,就题论题即可。

3.搜索插入位置

题目描述:

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

​ 输入: nums = [1,3,5,6], target = 5
​ 输出: 2

示例 2:

​ 输入: nums = [1,3,5,6], target = 2
​ 输出: 1

示例 3:

​ 输入: nums = [1,3,5,6], target = 7
​ 输出: 4

算法思路:

本题数组是一个排序数组,具有“二段性”时,我们就可以用二分查找算法。

  1. 设插入位置的坐标为x,根据插入位置的特点可以把数组的元素分为两部分——小于target的部分[left , x-1] and 大于等于target的部分[x , right]。
  • 当mid在[left, x-1]的区间,我们可以直接舍去[left, mid],更新left=mid+1;
  • 当mid在[x, right]的区间,我们可以直接舍去[mid+1,right],更新right=mid(因为mid可能是最终结果);
  • 之后在[left,right]上继续查找。
  1. 直到我们的查找区间的长度变为 1 ,也就是 left == right 的时候, left 或者right 所在的位置就是我们要找的结果。

代码实现:

class Solution
{
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) 
                left = mid + 1;
            else 
                right = mid;
        }
        if(nums[left] < target) 
            return right + 1;
        return right;
    }
};

4.x 的平方根

题目描述:

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

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

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

示例 1:

​ 输入:x = 4
​ 输出:2

示例 2:

​ 输入:x = 8
​ 输出:2

解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

解法一:暴力查找

算法思路:

依次枚举 [0, x] 之间的所有数 i :

  • 如果 i * i == x ,直接返回 x ;

  • 如果 i * i > x ,说明之前的⼀个数是结果,返回 i - 1 。

由于 i * i 可能超过 int 的最大值,因此使用 long long 类型。

代码实现:
class Solution {
public:
    int mySqrt(int x) {
        // 由于两个较⼤的数相乘可能会超过 int 最⼤范围
        // 因此⽤ long long
        long long i = 0;
        for (i = 0; i <= x; i++)
        {
            // 如果两个数相乘正好等于 x,直接返回 i
            if (i * i == x) return i;
            // 如果第⼀次出现两个数相乘⼤于 x,说明结果是前⼀个数
            if (i * i > x) return i - 1;
        }
        // 为了处理oj题需要控制所有路径都有返回值
        return -1;
    }
};

解法二:二分查找

本题数组具有“二段性”时,我们就可以用二分查找算法。

算法思路:

这里,我们把数组的元素分为两部分——平方后小于等于x的部分[1,mid] and 平方后大于x的部分[mid-1, x]。

  1. 定义 left , right 指针,分别指向数组的左右区间。

  2. 当left<right时,下列一直循环:

​ 找到待查找区间的中间点 mid ,找到之后分三种情况讨论:

  • mid*mid<=x:更新left=mid
  • mid*mid>x:更新right=mid-1
代码实现:
class Solution
{
public:
    int mySqrt(int x) 
    {
        if(x < 1) return 0; // 处理边界情况
        int left = 1, right = x;
        while(left < right)
        {
            long long mid = left + (right - left + 1) / 2; // 防溢出
            if(mid * mid <= x) left = mid;
            else right = mid - 1;
        }
        return left;
    }
};

最后,本篇文章到此结束,感觉不错的友友们可以一键三连支持一下笔者,有任何问题欢迎在评论区留言哦~

标签:二分,right,target,nums,int,mid,查找,习题,left
From: https://blog.csdn.net/zoelinkailuo/article/details/143610004

相关文章

  • 数据库SQL Server语言 练习题合集
    文章目录一、“小学生”题库二、“硕士”题库三、“博士”题库四、“博士后”题库五、“博导”题库六、“校长”题库七、“院士”题库总结1.**多表联接查询**:2.**嵌套查询(子查询)**:3.**去重与计数**:4.**条件筛选与分组**:5.**比较与计算**:6.**实际应用**:总结:一......
  • 二分图
    1基础概念二分图的概念在网络流学习中其实已经有所涉及。具体来讲,二分图就是节点由两个集合组成,且两个点集内部没有边的图。那么二分图有以下经典性质:二分图中没有奇环。证明显然。由于每一次走边一定是从一个点集走到另一个点集,如果要回到当前点集,那么必须要走偶数条边,因此......
  • Web组件和WebView 习题答案 <HarmonyOS第一课>
    一、判断题1. Web组件提供具有网页显示能力,@ohos.web.webview提供web控制能力。正确(True)错误(False)回答正确A2. 同一页面的多个Web组件,必须绑定不同的WebviewController。正确(True)错误(False)回答正确A二、单选题1. 下列关于Web组件的属性,描述错误的是?A.......
  • E. Reverse the Rivers(二分)CF984
    题意:给定n个国家,k个地区,aij为第i个国家第j个地区,bij=a1j|a2j|---aij为第i个国家第j个地区的更新值,给出q个问题,每个问题包含m项要求,国家i必须满足m项要求:如果o=='<'必须满足bir<c否则bir>c,输出满足所有条件的最小序号的国家分析:如果o是小于号,用二分找到右区间,如果o是大于号,用二......
  • 习题7.4
    importnumpyasnpfromscipy.interpolateimportinterp1d,interp2d,UnivariateSpline,griddataimportmatplotlib.pyplotaspltnp.random.seed(114514)x0=np.random.uniform(-3,3,50)y0=np.random.uniform(-4,4,50)f=lambdax,y:(x**2-2*x)*np.ex......
  • 习题7.1
    importnumpyasnpfromscipy.interpolateimportinterp1d,interp2d,UnivariateSpline,griddataimportmatplotlib.pyplotaspltfromscipy.integrateimportquadx0=np.linspace(0,10,1000)g=lambdax:(3*x**2+4*x+6)*np.sin(x)/(x**2+8*x+6)y0=g(x0)......
  • 二分(待续期待)
    思路:如何优雅的处理边界条件? 1.左边界、右边界的更新​先看一个例子:给定一个排好序的整数数组a,数组中可能存在重复元素。给定数组中的一个值target,求出它最后出现的位置。​例如数组a为:[13335],目标值target=3。a中最后一个等于3的元素为:a[3],所以结果为3。......
  • 查找串口
    查询串口fromserial.tools.list_portsimportcomports(fromserial.tools.list_ports_windowsimportcomports)print(comports())[<serial.tools.list_ports_common.ListPortInfoobjectat0x0000014FD6B0A8C0>,<serial.tools.list_ports_common.ListPortInfo......
  • ACWING 503. 借教室 二分+前缀和
    题目描述输入格式输出格式数据范围输入样例:432543213324424输出样例:-12题目分析 每个订单都是对第s到t这个区间进行操作,所以可以用差分和前缀来实现对这段区间的快速修改。在1-m个订单中一定会有最后一个能被处理的订单,因此可将1-m分为两个区......
  • 最该加训二分的一集
    今天校队布置作业了培训的时候靠直觉断定第二题存在数学公式直接求解而不需要二分,然后写了个循环扫了眼就得出了公式:不存在n%3==0的情况,所以遇到该情况需要"n++"(题目要求为至少多少支);然后此时记答案为ans,则n和ans满足n-ans=n/3(向下取整)然后就去做第一题了,扫了一眼就……就......