二分查找及其应用

概述

二分查找算法是一种效率极高的算法,也是为数不多时间复杂度在 O(logn)量级的算法。算法思想并不难理解,但是某些细节却十分复杂,因而本文尝试从一个通用框架入手,通过对不同细节的填补,生成在三种情况下适用的不同框架。同时后边给了一些二分查找的里边,便于读者练习。

框架与说明

通用二分查找框架

框架处理过程:

1. 初始化:为left和right赋值
2. 循环退出条件
3. 比较中值和目标值关系,分情况处理
    1. 相等
    2. 小于
    3. 大于

代码框架如下:

int binarySearch(int [] nums,int target) {
    int left = 0, right = ...;

    while(...){
        //防止溢出等同于mid = (left +right)/2
        int mid = left + (right - left)/2;
        if(nums[mid] == target) {
            ...

        }else if (nums[mid] < target) {
            left = ...

        }else if (nums[mid] > target) {
            right = ...

        }
    }
    return ...

}

分析上边的框架,可能有两个奇怪的地方:

第一个是mid的计算方法比较奇怪
第二个整个if判断过程中没有 else&#x5206;&#x652F;

其实这两个问题也是二分查找的两个重要点。

针对 第一点mid如果使用传统的写法 mid = (left + right)/2确实比较容易理解,但left和right直接相加可能会导致 上溢出的风险,因而需要使用 mid = left + (right - left)/2

第二个问题可能更多是一个 技巧,因为二分查找思想可能很容易理解,但是 细节却比较难以捉摸,因而在使用二分查找时要把所有情况用else if 写清楚,而不要出现else,这样可以清晰的展现出所有分支的细节,便于理解和排错。

同时,在整个模板中,我们可以看到有好多省略号 ...标记,这个是容易出现细节问题的地方,也是我们在使用二分查找需要尤为注意的问题,后边会结合一些简单实例,来说明一下这些细节会有那些变化。

基本二分查找

二分查找及其应用
public int binarySearch(int[] nums, int target) {
    // 初始化,细节一:right此处复制为nums.length - 1,
    //相当于搜索区间为[left,righty]
    int left = 0, right = nums.length - 1;
    // 循环退出条件,细节二:由于细节一的原因,此处要使用left target) {
        // 细节四:原因同细节三
        right = mid - 1;
      }
    }
    // 区间内所有值都已搜索完毕,直接返回-1
    return -1;
  }

基于初始模板,我们可以很快写出基本的二分查找算法,但是针对实现过程中的一些细节,我们需要做一些说明:

  1. 为什么 while 循环的条件中是

首先由于我们初始化的时候选择的右边界就是 right = nums.length -1,也即右边界是可以被访问到的,所以我们在终止条件判断的时候需要加上这个等号,搜寻的时候,搜索区间为 [left,right]一个闭区间。

这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为nums.length 是越界的。

  1. 为什么 left = mid + 1,right = mid – 1?我看有的代码是 right = mid 或者left = mid,没有这些加加减减,到底怎么回事,怎么判断?

这个也是二分查找的重要细节,刚才我们说了此处的搜索区间是[left,right],因此当判断mid之后,我们需要将搜索区间锁定在 [left,mid-1]、[mid+1,right]这两个区间中。后一种用法可能会在左边界查找右边界查找中会用到。

左边界查找

代码实现:

public int leftBound(int[] nums, int target) {
    // 细节1:right赋值为length,意味着搜索区间范围左闭右开
    int left = 0, right = nums.length;
    // 细节2
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 细节3
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            // 细节4
            right = mid;
        }
    }
    // 细节5
    return nums[left] == target ? left : -1;
}

左边界查找较之与基本二分查找多了几个细节点不同。

  1. 为什么 while(left < right) 而不是

答:用相同的方法分析,因为 right = nums.length 而不是 nums.length – 1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 为空,
所以可以正确终止。

  1. 为何返回left?

答:因为搜索退出时一定是left==right因此即使返回的是right也影响不大。

右边界查找

代码实现:

public int rightBound(int[] nums, int target) {
    int left = 0, right = nums.length;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            //细节1
            left = mid + 1;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    //细节2
    return nums[left - 1] == target ? left - 1 : -1;
}

这个相比左边界查找可能有两个细节地方改变:

  1. 中点值和目标值相同时,不直接返回而是要通过 left = mid + 1将查找区间向右逼近,进而才能查找最右侧的值
  2. 返回值是 left -1 ,主要是因为,在找到目标值之后做了一个left = mid +1 操作,从而实际我们想要的mid的值为left -1。

典型例题

1. 寻找旋转排序数组中的最小值

二分查找及其应用

基本思路:

在二分查找的过程中比较最左侧和最右侧值的大小,如果右侧小,则搜索【mid+1,right】区间,同时要注意一种情况,就是mid隔开了最小值,因此需要判断mid位置元素是否是最小的元素,如果不是将搜索区间改成【left,mid-1】。 如果左侧小则直接搜索【left,mid-1】

代码实现:

 public int findMin(int[] nums) {
    int left = 0, right = nums.length - 1;
    int min = nums[left];
    while (left > 1) + left;
      if (nums[mid] < min) {
        min = nums[mid];
      }
      if (nums[left] < nums[right]) {
        right = mid - 1;
      } else if (nums[left] > nums[right]) {
        // 证明最小值在mid的前面
        if (mid > left && nums[mid] > nums[mid - 1]) {
          right = mid - 1;
        } else {
          left = mid + 1;
        }
      } else if (nums[left] == nums[right]) {
        return min;
      }
    }
    return min;

总结

通过对上边三种二分查找框架的掌握,大部分二分查找问题都可以解决,还是那句话,二分查找本身思想比较简单,但是细节很折磨人,但是针对某个问题,逐个分析其细节,最后大部分问题应该还是可以比较好的解决的。

Original: https://www.cnblogs.com/goWithHappy/p/binary-search.html
Author: vcjmhg
Title: 二分查找及其应用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599429/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • windows下安装mysql5.7

    1.首先官网下载ZIP安装包(即以解压,配置的方式安装) 2.解压完成之后在目录下创建 my.ini文件 内容如下: [mysql]设置mysql客户端默认字符集default-c…

    数据库 2023年5月24日
    083
  • 看看你离世界一流大厂有多远?3道Google最新SQL面试题 ⛵

    💡 作者:韩信子@ShowMeAI📘 数据分析◉技能提升系列:https://www.showmeai.tech/tutorials/33📘 AI 面试题库系列:https://w…

    数据库 2023年6月14日
    078
  • 并发编程学习

    Semaphore Semaphore 可以允许多个线程访问一个临界区。 应用:实现线程池 CountDownLatch 应用: 业务原始状态:一个线程执行查询订单,查询派送单,对…

    数据库 2023年6月16日
    099
  • Hello Word

    编写代码 public class  hello{ public static void main(String[] args){ System.out.print("H…

    数据库 2023年6月11日
    063
  • python-图片文字识别

    两种方法 1. 第一种方法 from PIL import Image import pytesseract import re #&#x5BFC;&#x5165;…

    数据库 2023年6月14日
    079
  • 阿里慢SQL治理5大经典案例

    菜鸟供应链金融慢sql治理已经有一段时间,自己负责的应用持续很长时间没有慢sql告警,现阶段在推进组内其他成员治理应用慢sql。这里把治理过程中的一些实践拿出来分享下。 一、全表扫…

    数据库 2023年5月24日
    0129
  • BigDecimal 设置小数位数、小数比例转换整数

    控制小数位数 DecimalFormat decimalFormat = new DecimalFormat("0.00"); decimalFormat.fo…

    数据库 2023年6月6日
    092
  • 2022-08-17 DQL—-子查询,日期格式

    子查询、日期格式 DQL查询语言 子查询 按照结果集的行列数不同,子查询可以分为以下几类: 标量子查询:结果集只有一行一列(单行子查询) 列子查询:结果集有一列多行 行子查询:结果…

    数据库 2023年6月14日
    093
  • Redis 生产架构选型对比,一文整治选择困难症

    前言 在写开源项目的时候,想到了要支持多种redis部署方式,于是对于这块的生产环境的架构选型展开调研。 一、引擎版本 推荐使用更新的引擎版本以支持更多的特性, Redis 6.0…

    数据库 2023年6月14日
    090
  • mysql常用操作汇总

    工作中经常用会遇到这种情况,可以访问mysql所在的服务器,但是服务器端口不对外暴露(通常因为安全原因)。这时,操作数据库只能通过命令行和 mysql client窗口来实现。我对…

    数据库 2023年5月24日
    081
  • MyBatis-Plus入门教程及基本API使用案例

    一、MyBatisPlus简介 1. 入门案例 问题导入 MyBatisPlus环境搭建的步骤? 1.1 SpringBoot整合MyBatisPlus入门程序 ①:创建新模块,选…

    数据库 2023年6月16日
    0103
  • JavaWeb 05_JDBC入门及连接MySQL

    一、概念 *概念: Java DataBase Connectivity Java数据库连接, Java语言操作数据库* JDBC本质:其实是官方(sun公司)定义的一套操作所有关…

    数据库 2023年5月24日
    0107
  • SQL 版本号排序

    SQL 语句直接对内容为版本号格式的字段进行排序时,排序效果通常不是最终想要的效果,因为最终需要的效果,是需对版本号里的每一段(通常以小数点分隔)按数值进行排序。 解决这个问题,主…

    数据库 2023年5月24日
    075
  • leetcode 538. Convert BST to Greater Tree 把二叉搜索树转换为累加树(简单)

    一、题目大意 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node…

    数据库 2023年6月16日
    090
  • MySQL中常用的数据类型

    在写sql语句的时候,数据类型是避不可少的一个环节,以下是我在学习的过程中总结的数据类型,仅供参考: 数值类型 您可以在上表中看到,每种类型都有其对应的范围。如果它大于某个值,则不…

    数据库 2023年5月24日
    094
  • Linux 服务管理

    Linux 服务管理 1. 基本介绍 服务的本质就是进程,但是是运行在后台的,通常都会监听某个端口,等待其它程序的请求,比如mysqld,sshd,防火墙等,因此我们又称为守护线程…

    数据库 2023年6月6日
    0102
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球