• 【数据结构与算法】二分查找算法


    ​🌠 作者:@阿亮joy.
    🎆专栏:《数据结构与算法要啸着学》
    🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
    在这里插入图片描述

    活动地址:CSDN21天学习挑战赛

    👉前言👈

    相信大家之前已经过学习二分查找算法了,也知道二分查找算法使用的前提:严格有序的数组。那是不是永远都需要满足这个前提才能使用二分查找算法呢?本文将给出一些二分查找算法类型的题目,与你一探究竟。

    👉二分查找👈

    给定一个 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]之间。

    以上就是二分查找的母体了,这是最简单、最基础的。现在我们来写代码实现它。
    在这里插入图片描述

    int search(int* nums, int numsSize, int target) 
    {
        int left = 0;
        int right = numsSize - 1;
        while (left <= right)
        {
            int mid = left + (right - right) / 2;
            if (nums[mid] > target)
            {
                right = mid - 1;
            }
            else if (nums[mid] < target)
            {
                left = mid + 1;
            }
            else
                return mid;
        }
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    分析:二分查找算法有左闭右闭区间和左闭右开区间两种写法,博主的二分查找算法的写法均采取的左闭右闭区间的写法。采用左闭右闭区间写法时,当 nums[mid] > target 时,right = mid - 1;当 nums[mid] < target 时,left = mid + 1;当nums[mid] == target 时,直接 return mid。还需要注意的是 mid 要定义在 while 循环内部。

    👉猜数字大小👈

    猜数字游戏的规则如下:

    • 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
    • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

    你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

    • -1:我选出的数字比你猜的数字小 pick < num

    • 1:我选出的数字比你猜的数字大 pick > num

    • 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num


    示例 1:

    输入:n = 10, pick = 6
    输出:6


    示例 2:

    输入:n = 1, pick = 1
    输出:1


    提示:

    • 1 <= n <= 2^31 - 1
    • 1 <= pick <= n
    
    int guessNumber(int n){
          int left=1;
          int right=n;
          int mid=0;
          while(left<=right)
          {
              mid=left+(right-left)/2;
              if(guess(mid)==1)
              {
                  left=mid+1;
              }
              else if(guess(mid)==-1)
              {
                  right=mid-1;
              }
              else
                  return mid;
          }
          return mid;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    分析:本题最需要注意的就是,接口函数 guess 返回值的意思,其他的写法都是按照二分查找算法的思路写就行了。

    👉搜索插入位置👈

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

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


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


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


    提示:

    • 1 <= nums.length <= 104
    • -104 <= nums[i] <= 104
    • nums 为 无重复元素 的 升序 排列数组
    • -104 <= target <= 104

    在这里插入图片描述

    int searchInsert(int* nums, int numsSize, int target) {
    
        int left = 0;
        int right = numsSize - 1;
        if (nums[right] < target)
            return numsSize;
        if (nums[0] > target)
            return 0;
        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 left;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    分析:当数组 nums 的最后一个元素小于 target 时,那么插入位置就是 numsSize;当数组 nums 的第一个元素大于 target 时,那么插入位置就是0。如果不符合上面两种情况的话,就会进入 while 循环。如果数组 nums 中包含了target,那么二分查找就会找到其下标 mid,也就是插入位置,将插入位置 return 就行了。如果数组 nums 不包含target,那么 left 迟早会大于 right 退出 while 循环,此时 left 就是插入位置,将其 return 就行了。

    👉山脉数组的峰顶索引👈

    符合下列属性的数组 arr 称为 山脉数组 :

    • arr.length >= 3
    • 存在 i(0 < i < arr.length - 1)使得:
      • arr[0] < arr[1] < … arr[i-1] < arr[i]
      • arr[i] > arr[i+1] > … > arr[arr.length - 1]

    给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i 。


    示例 1:
    输入:arr = [0,1,0]
    输出:1


    示例 2:
    输入:arr = [0,2,1,0]
    输出:1


    提示:

    • 3 <= arr.length <= 104
    • 0 <= arr[i] <= 106
    • 题目数据保证 arr 是一个山脉数组
    int peakIndexInMountainArray(int* arr, int arrSize)
    {
    
        int left = 1, right = arrSize - 2;
        int mid = 0;
        while (left <= right)
        {
            mid = left + (right - left) / 2;
            if (arr[mid] < arr[mid + 1])
            {
                left = mid + 1;
            }
            else if (arr[mid] < arr[mid - 1])
            {
                right = mid - 1;
            }
            else
                return mid;
        }
        return mid;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    分析:该题的二分查找算法和上面的题差不多,当时唯一不同的就是 left 的起始位置是0,right的起始位置是 numsSize - 1,这样初始化的目的是避免数组越界访问。

    👉有效的完全平方数👈

    给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

    进阶:不要 使用任何内置的库函数,如 sqrt 。


    示例 1:
    输入:num = 16
    输出:true


    示例 2:
    输入:num = 14
    输出:false


    提示:

    • 1 <= num <= 2^31 - 1
    bool isPerfectSquare(int num){
        int left=0;
        int right=num;
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            long square=(long)mid*mid;
            //将mid*mid强制类型转换为long,防止溢出
            if(square>num)
            {
                right=mid-1;
            }
            else if(square<num)
            {
                left=mid+1;
            }
            else
                return true;
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    分析:当不满足循环条件时,说明1到 num 之间没有任何一个数的平方等于 num,所以 return false。

    👉x 的平方根👈

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

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

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


    示例 1:

    输入:x = 4
    输出:2


    示例 2:

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


    提示:

    • 0 <= x <= 231 - 1
    int mySqrt(int x) 
    {
        int left = 0;
        int right = x;
        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            long  n = (long)mid * mid;
            //强制类型转换,防止溢出
            if (n > x)
            {
                right = mid - 1;
            }
            else if (n < x)
            {
                left = mid + 1;
            }
            else
                return mid;
        }
        return right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    分析: 因为退出循环是 left > right,而 left * left 是大于 x 的,right * right是小于 x 的,所以return right。

    👉寻找比目标字母大的最小字母👈

    给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。

    在比较时,字母是依序循环出现的。举个例子:

    • 如果目标字母 target = ‘z’ 并且字符列表为 letters = [‘a’, ‘b’],则答案返回 ‘a’

    示例 1:

    输入: letters = ["c", "f", "j"],target = "a"
    输出: "c"


    示例 2:

    输入: letters = ["c","f","j"], target = "c"
    输出: "f"


    提示:

    • 2 <= letters.length <= 104
    • letters[i] 是一个小写字母
    • letters 按非递减顺序排序
    • letters最少包含两个不同的字母
    • target 是一个小写字母
    char nextGreatestLetter(char* letters, int lettersSize, char target)
    {
         if(target>=letters[lettersSize-1])
            return letters[0];
         int left=0;
         int right=lettersSize-1;
         while(left<=right)
         {
             int mid=left+(right-left)/2;
             if(letters[mid]>target)
             {
                 right=mid-1;
             }
             else
             {
                 left=mid+1;
             }  
         }
         return letters[left];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    👉总结👈

    以上就是二分查找算法的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!!!💖💝❣️

  • 相关阅读:
    c语言从入门到实战——数组指针与函数指针
    Open3D(C++)欧式聚类分割
    【Vue】computed方法生成计算属性
    CentOS与Xshell,开启ssh协议sshd服务远程控制
    LeetCode:字符串篇
    微信小程序(五)--- Vant组件库,API Promise化,MboX全局数据共享,分包相关
    【 OpenGauss源码学习 —— 列存储(CStore)(一)】
    图片上传~
    一、项目创建与角色移动
    工业数采网关 工业数采模块 工业数采工业数采终端硬件
  • 原文地址:https://blog.csdn.net/m0_63639164/article/details/126214098