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

简介: 【数据结构与算法】二分查找算法

👉前言👈


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


👉二分查找👈


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


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

833d79a6d5124af080bd8b8826a0509a.png

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

6536f5e7355f4570935d667ec247ae8d.jpg


分析:二分查找算法有左闭右闭区间和左闭右开区间两种写法,博主的二分查找算法的写法均采取的左闭右闭区间的写法。采用左闭右闭区间写法时,当 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;
}

b04659183c0740d19de6bdbb2a9583f2.png


分析:本题最需要注意的就是,接口函数 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

e5e1ad49720247b3a43bbe6107611c5e.png


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

6f3ba10ffc3143a286e22683b035ea80.png


分析:当数组 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;
}

7206177d48f042b49a66278866fba20a.png


分析:该题的二分查找算法和上面的题差不多,当时唯一不同的就是 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;
}

4153bd57aca54027b7cc834ee87ef14b.png


分析:当不满足循环条件时,说明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;
}

f42a02f8a8444514b999d8752dba9339.png


分析: 因为退出循环是 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];
}

9a00f736a2964f1fb4ac65ec3e4db3a0.png


👉寻找旋转排序数组中的最小值 II👈


已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums =[0,1,4,4,5,6,7] 在变化后可能得到:

若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]

若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]


注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组[a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。



给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素。


示例 1:


输入:nums = [1,3,5]

输出:1


示例 2:


输入:nums = [2,2,2,0,1]

输出:0


提示:


n == nums.length

1 <= n <= 5000

-5000 <= nums[i] <= 5000

nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转


思路:因为 nums 数组是经有序数组旋转得来的,所以 nums 数组左边的元素一定大于右边的元素(除旋转 n 次)。那么旋转数组的最小值肯定是在 nums 数组的右边,所以我们将 nums[mid] 和 nums[right] 进行比较。因为可能有重复的元素,所以存在三种比较结果。


nums[mid] > nums[right]时,left = mid + 1,去除掉不是最小的数字

nums[mid] = nums[right]时,right--,去除掉重复的数字或逼近循环条件

nums[mid] < nums[right]时,right = mid,保留目前最小的数字

循环结束,nums[left] 就是旋转数组的最小数字


int findMin(int* numbers, int numbersSize)
{
   int left = 0;
   int right = numbersSize-1;
   while(left<=right)
   {
       int mid=left+(right-left)/2;
       if(numbers[mid]>numbers[right])
       {
           left=mid+1;
       }
       else if(numbers[mid]<numbers[right])
       {
           right=mid;
       }
       else
       {
           right--;
       }
   }
   return numbers[left];
}


40993425246c460bbcd89d7113a7bcb8.png

分析:这道题的解法也适用于《寻找旋转排序数组中的最小值 I》,因为这个解法考虑的情况更加的全面,包含《寻找旋转排序数组中的最小值 I》中的所有情况。


👉总结👈


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


相关文章
|
10天前
|
存储 算法 搜索推荐
软考算法破壁战:从二分查找到堆排序,九大排序核心速通指南
专攻软考高频算法,深度解析二分查找、堆排序、快速排序核心技巧,对比九大排序算法,配套动画与真题,7天掌握45%分值模块。
51 0
软考算法破壁战:从二分查找到堆排序,九大排序核心速通指南
|
2月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
69 1
|
2月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
79 0
|
8月前
|
存储 算法 C++
【C++数据结构——查找】二分查找(头歌实践教学平台习题)【合集】
二分查找的基本思想是:每次比较中间元素与目标元素的大小,如果中间元素等于目标元素,则查找成功;顺序表是线性表的一种存储方式,它用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的元素在物理存储位置上也相邻。第1次比较:查找范围R[0...10],比较元素R[5]:25。第1次比较:查找范围R[0...10],比较元素R[5]:25。第2次比较:查找范围R[0..4],比较元素R[2]:10。第3次比较:查找范围R[3...4],比较元素R[3]:15。,其中是顺序表中元素的个数。
295 68
【C++数据结构——查找】二分查找(头歌实践教学平台习题)【合集】
|
6月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
187 10
 算法系列之数据结构-二叉树
|
6月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
156 3
 算法系列之数据结构-Huffman树
|
7月前
|
算法 Java 索引
算法系列之搜素算法-二分查找
二分查找是一种在`有序`数组中查找特定元素的算法。它的基本思想是通过将数组分成两半,逐步缩小查找范围,直到找到目标元素或确定目标元素不存在。
93 9
算法系列之搜素算法-二分查找
|
6月前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
188 22
|
7月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
195 30
|
7月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
302 25

热门文章

最新文章