算法篇:二分查找

目录

介绍

查找数组中值算法模板

左右边界模板

实例

二分查找(easy)

在排序数组中查找元素的第一个和最后一个(medium)

搜索插入位置(easy)

[x 的平方根(easy)](#x 的平方根(easy))

山峰数组的峰顶(easy)

寻找峰值(medium)

搜索旋转排序数组中的最小值(medium)

[0〜n-1 中缺失的数字(easy)](#0〜n-1 中缺失的数字(easy))


介绍

核心思想:每次都通过比较中间元素,将查找范围缩小一半,直到找到目标或确认目标不存在。

必要条件:

  • 数据结构一定要是有序的(升序或降序)
  • 支持随机访问(如数组、列表),链表不适合

查找数组中值算法模板

左右边界模板

左边界:

右边界:

如何确定题目到底是用哪个边界的算法呢?

  • 可以直接假设mid在我们想找的答案上,然后根据上面的模板和判断条件的要求,看看要缩小那一边的区域。
  • 左边界找的是满足判断条件的第一个数,右边界是找满足条件的第二个数

实例

二分查找(easy)

704. 二分查找 - 力扣(LeetCode)

该题目中的数组是升序的,可以考虑用二分查找。

算法流程:

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

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

  • arr[mid] == target 说明正好找到,返回 mid 的值;
  • arr[mid] > target 说明 [mid, right] 这段区间都是大于 target 的,因此舍去右边区间,在左边 [left, mid -1] 的区间继续查找,即让 right = mid -1 ,然后重复 2 过程;
  • arr[mid] < target 说明 [left, mid] 这段区间的值都是小于 target 的,因此舍去左边区间,在右边 [mid + 1, right] 区间继续查找,即让 left = mid +1 ,然后重复 2 过程;

c. 当 left 与 right 错开时,说明整个区间都没有这个数,返回 -1 。

复制代码
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)left=mid+1;
            else if  (nums[mid]>target)right=mid-1;
            else return mid;
        }
        return -1;
    }
};

在排序数组中查找元素的第一个和最后一个(medium)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

题目解析:在一个升序数组中,找到一段全是目标值的区间,返回全是目标值的第一个下标和最后一个目标值的下标。

假设左边界下标是resleft,目标值为x,右边界下标为resright

寻找左边界思路:

根据左边界进行划分成两个区域:

  • left,resleft-1\]都是小于x的

因此根据mid的落点,我们可以分为两个情况

  • 当mid落在[left,resleft-1]区域内是,即nums[mid]<x,那么这个区域内的值是可以全部舍去的,所以让left=mid+1,继续在[mid+1,right]中寻找左边界
  • 当mid落在[resleft,right]区域内,即nums[mid]>=x,说明[mid+1,right]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在[left,mid]中寻找左边界

寻找右边界思路:

根据右边界进行划分成两个区域:

  • left,resright\]都是小于等于x的

因此根据mid的落点,我们可以分成两种情况:

  • 当mid落在[left,resright]区域内是,即nums[mid]<=x,那么[left,mid-1]的值是可以全部舍去的,但mid位置可能是最终结果,所以让left=mid,继续在[mid,right]中寻找右边界
  • 当mid落在[resright+1,right]区域内,即nums[mid]>x,说明[mid,right]可以舍去,所以right更新成mid-1,继续在[left,mid-1]中寻找右边界

细节部分:

中间值有两种取法:

mid=left+(right-left)/2 //向下取整

mid=left+(right-left+1)/2//向上取整

当left和right即将相遇时,可能出现死循环:

寻找左边界时:假设left=1,right=2,left=mid+1,right=mid,如果mid=left+(right-left+1)/2,那么mid=2,因为right=mid,导致一直right都没有向前走一步,让left==right让循环结束,所以我们寻找左边界的时候要向下取整。

同理可得右边界

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

        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};
    }
};

搜索插入位置(easy)

35. 搜索插入位置 - 力扣(LeetCode)

题目解析:在一个升序数组中找到target的下标,如果target不存在就返回它在数组中处在什么下标可以让数组依旧是升序所在的下标。

根据第二个案例,假设mid在下标为1的地方,然后当前数组中的下标为1的数大于target(3>2),判断条件是nums[mid]与target的关系,3>2,说明题意要求的值在左边,所以可以直接舍去右边的值,这时候就是right移动,right有两种移动,一个是right=mid,另一个是right=mid-1。如果选择第二个,那么正确答案就被排除在外了,所以选第一个,第一个在左右边界模板中寻找左边界模板。

解题思路:

假设target所在数组中的下标是i;

那么我们可以把数组分为两个区域:

  • left,i-1\]:全部小于target;

根据这个区域划分,我们可以知道其实这个题相当于找左边界。

因此根据mid的落点,我们可以分为两个情况

  • 当mid落在[left,i-1]区域内是,即nums[mid]<x,那么这个区域内的值是可以全部舍去的,所以让left=mid+1,继续在[mid+1,right]中寻找

  • 当mid落在[i,right]区域内,即nums[mid]>=x,说明[mid+1,right]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在[left,mid]中寻找

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

x 的平方根(easy)

69. x 的平方根 - 力扣(LeetCode)

题目解析:找出x的平方根,即给x开根号,如果x开根号后有小数就只保留整数。

根据第二个案例,假设mid在等于2的地方,然后当前数组中的2*2的数小于target(4<8),找的值在右边,,所以舍去左边的值,这时候就是left移动,left有两种移动,一个是left=mid,另一个是left=mid+1。如果选择第二个,那么正确答案就被排除在外了,所以选第一个,第一个在左右边界模板中寻找右边界模板。

解题思路:假设x的最终平方根结果是index

把区域划分为两部分:

left,index\]的平方根全部都是小于等于x的 \[index+1,right\]的平方根全部大于x 具有二分性 因此根据mid的落点,我们可以分成两种情况: * 当mid落在\[left,i\]区域内是,即nums\[mid\]\<=x,那么\[left,mid-1\]的值是可以全部舍去的,但mid位置可能是最终结果,所以让left=mid,继续在\[mid,right\]中寻找 * 当mid落在\[i+1,right\]区i域内,即nums\[mid\]\>x,说明\[mid,right\]可以舍去,所以right更新成mid-1,继续在\[left,mid-1\]中寻找 class Solution { public: int mySqrt(int x) { long long left=1,right=x; if(x==0)return 0; while(left& arr) { int n = arr.size(); // 遍历数组内每⼀个元素,直到找到峰顶 for (int i = 1; i < n - 1; i++) // 峰顶满⾜的条件 if (arr[i] > arr[i - 1] && arr[i] > arr[i + 1]) return i; // 为了处理 oj 需要控制所有路径都有返回值 return -1; } }; 方法二: 根据峰值所在位置可以把数组分成两部分,一部分单调递增,一部分单调递减,具有二分性。 根据第二个案例,假设mid在下标为1的地方,然后当前数组中的下标为1的数大于后一个数,说明目标值在左边(前半部分单调递增),可以舍去右边的区域,这时候就是right移动,right有两种移动,一个是right=mid,另一个是right=mid-1。如果选择第二个,那么正确答案就被排除在外了,所以选第一个,第一个就是左右边界模板中寻找左边界模板。 解题思路: 假设target所在数组中的下标是i; 那么我们可以把数组分为两个区域: * \[left,i-1\]:全部小于target; * \[i,right\]都是大于等于target 根据这个区域划分,我们可以知道其实这个题相当于找左边界。 因此根据mid的落点,我们可以分为两个情况 * 当mid落在\[left,i-1\]区域内是,即nums\[mid\]\=x,说明\[mid+1,right\]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在\[left,mid\]中寻找 class Solution { public: int peakIndexInMountainArray(vector& arr) { int left=0,right=arr.size()-1; while(leftarr[mid+1])right=mid; else left=mid+1; } return right; } }; ### 寻找峰值(medium) [162. 寻找峰值 - 力扣(LeetCode)](https://leetcode.cn/problems/find-peak-element/ "162. 寻找峰值 - 力扣(LeetCode)") ![](https://i-blog.csdnimg.cn/direct/4fbb1c7e4458457ca9d440732ef4da08.png) 解题方法和上题一样 class Solution { public: int findPeakElement(vector& nums) { int left=0,right=nums.size()-1; while(leftnums[mid+1])right=mid; else left=mid+1; } return right; } }; ### 搜索旋转排序数组中的最小值(medium) [153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/description/ "153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)") ![](https://i-blog.csdnimg.cn/direct/c3242a642ba942ea8e12f2310dbc03ec.png) 题目解析:找一个数组中的最小值。这个数组中有两个都是单调递增的 方法一:暴力解法 一直标记一个最小值进行比较 class Solution { public: int findMin(vector& nums) { int n = nums.size(); int minnum=INT_MAX; int index=-1; // 遍历数组内每⼀个元素,直到找到最小值 for (int i = 0; i < n; i++) { if(nums[i]=x,说明\[mid+1,right\]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在\[left,mid\]中寻找 class Solution { public: int findMin(vector& nums) { int left=0,right=nums.size()-1; //找最小值 int x=nums[right]; while(left& records) { if(records.size()==1&&records[0]==0)return 1; for(int i=0;i& records) { if(records.size()==1&&records[0]==0)return 1; int left=0,right=records.size()-1; while(left

相关推荐
垫脚摸太阳1 小时前
二分查找经典算法题--数的范围
数据结构·算法
setmoon2141 小时前
C++中的构建器模式
开发语言·c++·算法
2301_815482931 小时前
C++中的桥接模式变体
开发语言·c++·算法
yunyun321231 小时前
C++与量子计算模拟
开发语言·c++·算法
吴声子夜歌1 小时前
JavaScript——数组
java·javascript·算法
不知名。。。。。。。。2 小时前
仿muduo库实现高并发服务器----HttpServer
运维·服务器·算法
暮冬-  Gentle°2 小时前
移动设备上的C++优化
开发语言·c++·算法
2401_874732532 小时前
C++中的装饰器模式高级应用
开发语言·c++·算法
季明洵2 小时前
回溯介绍及实战
java·数据结构·算法·leetcode·回溯