【算法】二分查找

一、算法描述

需求:在有序数组 A 内,查找值 target;如果找到返回索引,如果找不到返回 -1

1.描述

给定一个含 n 个元素的有序数组 A,满足 A0 ≤ A1 ≤ A2 ≤···≤ An-1,一个待查值 target。

2.解题思路

因为数组是有序的,所以用区间减半来提高查找效率,左区间起点i,右区间起点j,一开始是全部元素,每次拿中间的元素来和目标元素比较,如果相等则返回;
如果目标元素小于中间元素,则说明目标元素在左区间,直接把区间范围缩减到左区间。
如果目标元素大于中间元素,则说明目标元素在右区间,直接把区间范围缩减到右区间。
一直重复,直到找到目标元素。

3.解题步骤
  • 1.设置 i= 0, j=n-1
  • 2.如果 i > j,结束查找,没找到
  • 3.设置 m = floor((i+j)/2),m 为中间索引,floor 是向下取整( ≤(i+j)/2的最小整数)
  • 4.如果 target < Am , 设置 j = m - 1,跳到第2步
  • 5.如果 target > Am , 设置 i = m + 1,跳到第2步
  • 6.如果 target = Am ,找到了,结束查找。

二、使用左闭右闭

    /**
     * 二分查找基础版
     * @param arr
     * @param target
     * @return
     */
    public static int binarySearchBasic(int[] arr,int target) {
        int i = 0;
        int j = arr.length - 1;

        while (i <= j) {
            int mid = (i + j) / 2;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else if (target > arr[mid]) {
                // 去到右区间查找
                i = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

判断的时候使用的是i <= j,如果没有等于的话,那么参与比较的只有i和j中间的元素,i和j本身就没有参与比较,如果目标就是i和j,则会返回-1。

三、越界问题

当数组的数量达到了整数最大值个数的时候,使用(i+j)/2 运算,当要查找的目标在右区间时,运算的过程中i+j超过了整数的最大值,那么结果会变成负数。
在Java中,数字的表示都是带符号位的,虽然i+j已经超过了整数最大的表示范围,但(i+j)/2并没有超过,所以可以把除以2替换为右移运算,虽然i+j是负数,但是实际他对应的二进制数是没有问题的,只是在Java中,是带符号位的,所以就表现了负数,在使用右移替换了除法之后,得到的数就没有超过最大值了,就可以准确的转换为正确的数字了。
优化代码:

    /**
     * 二分查找基础版
     * @param arr
     * @param target
     * @return
     */
    public static int binarySearchBasic(int[] arr,int target) {
        int i = 0;
        int j = arr.length - 1;

        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else if (target > arr[mid]) {
                // 去到右区间查找
                i = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

四、使用左闭右开

让j只作为边界,而不作为查找目标。

    /**
     * 二分查找基础版 左闭右开
     * @param arr
     * @param target
     * @return
     */
    public static int binarySearch(int[] arr,int target) {
        int i = 0;
        int j = arr.length; // 第一处不同

        while (i < j) {    // 第二处不同
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid;   // 第三处不同
            } else if (target > arr[mid]) {
                // 去到右区间查找
                i = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

注意:这里的判断,一定要使用<,不能是<=,因为j不作为比较的元素了,只作为边界,否则会在没有元素的情况下进入死循环。

五、平衡二分查找

在上面的写法中:
如果要查找的元素在最左边,那么判断中,只需要执行if判断,不需要执行else if判断,一共执行的次数是n次;
如果要查找的元素在最右边,那么判断中,if判断执行了,但是没满足,else if判断也执行,满足,所以一共执行的次数是2n次。
所以查找并不平衡。

优化思路:把多执行的else if判断抽出去,让每次操作都只执行一次判断,那么比较次数就不存在不平衡的情况了。

/**
     * 平衡二分查找
     * @param arr
     * @param target
     * @return
     */
    public static int balanceBinarySearch(int[] arr, int target) {
        int i = 0;
        int j = arr.length;

        while (j - i > 1) {
            //当i和j中间还有元素的时候就继续
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                j = mid;
            } else {
                i = mid;
            }
        }
        if (arr[i] == target) {
            return i;
        } else {
            return -1;
        }
    }

1.左闭右开的区间,i 指向的可能是目标,而 j 指向的不是目标
2.不在循环内找出目标对象,而是等范围内只剩i时,退出循环,在循环外比较 a[i] 与 target
3.循环内的平均比较次数减少了
4.时间复杂度 θ ( l o g ( n ) ) θ(log(n)) θ(log(n))

缺点:无论什么情况,都要遍历完直到只剩下最终的元素,才会退出循环拿到结果。时间复杂度最好和最快都是 θ ( l o g ( n ) ) θ(log(n)) θ(log(n))

六、二分查找在Java中的实现

Arrays.binarySearch(int[] a, int key)

    /**
     * Searches the specified array of ints for the specified value using the
     * binary search algorithm.  The array must be sorted (as
     * by the {@link #sort(int[])} method) prior to making this call.  If it
     * is not sorted, the results are undefined.  If the array contains
     * multiple elements with the specified value, there is no guarantee which
     * one will be found.
     *
     * @param a the array to be searched
     * @param key the value to be searched for
     * @return index of the search key, if it is contained in the array;
     *         otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>.  The
     *         <i>insertion point</i> is defined as the point at which the
     *         key would be inserted into the array: the index of the first
     *         element greater than the key, or <tt>a.length</tt> if all
     *         elements in the array are less than the specified key.  Note
     *         that this guarantees that the return value will be &gt;= 0 if
     *         and only if the key is found.
     */
    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }

    // Like public version, but without range checks.
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }

七、二分查找对重复元素的处理

查找目标元素,如果有相同的元素,返回最左侧/最右侧的元素

    /**
     * 二分查找,返回最左侧的目标元素
     * @return
     */
    public static int binarySearchLeftMost(int[] arr, int target) {
        int i = 0;
        int j = arr.length - 1;
        int candidate = -1 ;
        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else if (target > arr[mid]) {
                // 去到右区间查找
                i = mid + 1;
            } else {
                //相等了不立刻返回,而是找到最左侧的,再返回
                candidate = mid;
                //继续向左查找,看看是否还有相同的
                j = mid - 1;
            }
        }
        return candidate;
    }

    /**
     * 二分查找,返回最右侧的目标元素
     * @return
     */
    public static int binarySearchRightMost(int[] arr, int target) {
        int i = 0;
        int j = arr.length - 1;
        int candidate = -1 ;
        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else if (target > arr[mid]) {
                // 去到右区间查找
                i = mid + 1;
            } else {
                //相等了不立刻返回,而是找到最右侧的,再返回
                candidate = mid;
                //继续向左查找,看看是否还有相同的
                i = mid + 1;
            }
        }
        return candidate;
    }

优化-1返回值:让找不到的时候,返回一个接近的元素下标,而不是-1

    /**
     * 优化最左查找返回值
     * @param arr
     * @param target
     * @return 返回大于等于目标的最靠左的索引
     */
    public static int binarySearchLeftMost1(int[] arr, int target) {
        int i = 0;
        int j = arr.length - 1;
        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (target <= arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else {
                // 去到右区间查找
                i = mid + 1;
            }
        }
        return i;
    }


    /**
     * 优化最右查找返回值
     * @param arr
     * @param target
     * @return 小于等于目标的最靠右的索引
     */
    public static int binarySearchRightMost1(int[] arr, int target) {
        int i = 0;
        int j = arr.length - 1;
        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (target < arr[mid]) {
                // 去到左区间查找
                j = mid - 1;
            } else {
                // 去到右区间查找
                i = mid + 1;
            }
        }
        return i - 1;
    }

八、Leetcode相关题目:

1.Leetcode704.二分查找

给定一个 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
class Solution {
    public int search(int[] nums, int target) {
        int i = 0;
        int j = nums.length -1 ;
        while(i<=j){
            int mid = (i+j) >>> 1;
            if(target < nums[mid]){
                j = mid -1;
            }else if(target > nums[mid]){
                i = mid +1;
            }else{
                return mid;
            }
        }
        return -1;
    }
}
2.Leetcode35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
class Solution {
    public int searchInsert(int[] nums, int target) {
        int i = 0;
        int j = nums.length-1;
        while(i<=j){
            int mid = (i+j) >>> 1;
            if(target<=nums[mid]){
                j = mid -1;
            }else {
                i = mid +1;
            }
        }
        return i;
    }
}
3.Leetcode34. 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 查找最左侧元素
        int left = searchMost(nums,target,-1);
        if(left == -1){
            return new int[]{-1,-1};
        }else{
            // 查找最右侧元素
            int right = searchMost(nums,target,1);
            return new int[]{left,right};
        }
    }

    
    public int searchMost(int[] nums,int target,int lr){
        int i = 0;
        int j = nums.length - 1 ;
        int candidate = -1;
        while(i<=j){
            int mid = (i+j) >>> 1;
            if(target < nums[mid]){
                j = mid -1;
            }else if(target > nums[mid]){
                i = mid + 1;
            }else {
                candidate = mid;
                // 最左元素
                if(lr == -1){
                    j = mid - 1;
                }else{
                    //最右元素
                    i = mid + 1;
                }
            }
        }
        return candidate;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/604798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaEE企业级开发中常用的JDK7和JDK8的时间类

JDK7时间类 全世界的时间有一个统一的计算标准 在同一条经线上的时间是一样的 格林威治时间 简称GMT 计算核心 地球自转一天是24小时 太阳直射正好是12小时 但是误差太大 现在用原子钟来代替 用铯原子震动的频率来计算时间&#xff0c;作为世界的标准时间UTC 中国标准时间…

Dockerfile实践java项目

目的&#xff1a;用java项目测试dockerfil部署&#xff08;前提是安装好了docker&#xff09; 部署准备文件如下 1. java项目 java项目demo地址 https://gitee.com/xiaoqu_12/dockerfileDemo.git 或者百度网盘直接下载打包好的jar包 链接&#xff1a;https://pan.baidu.com/s/…

Ansible---inventory 主机清单

一、inventory 主机清单 1.1、inventory介绍 hosts配置文件位置&#xff1a;/etc/ansible/hosts Inventory支持对主机进行分组&#xff0c;每个组内可以定义多个主机&#xff0c;每个主机都可以定义在任何一个或多个主机组内。 1.2、inventory中的变量 Inventory变量名含义…

数值计算方法——大题题型总结

目录 一、绝对误差限、相对误差限 1.1 例题 1.2 解题套路 1.3 题解 二、敛散性、收敛速度 2.1 例题 2.2 解题套路 2.3 题解 三、牛顿迭代法 3.1 例题 3.2 解题套路 3.3 题解 四、割线法 4.1 例题 4.2 解题套路 ​4.3 题解 五、列主元素消去法 5.1 例题 5.…

新版Idea配置仓库教程

这里模拟的是自己搭建的本地仓库环境&#xff0c;基于虚拟机搭建利用gogs创建的仓库 1、Git环境 你需要准备好git和仓库可以使用github 、gitee等 1.1 拉取代码 本项目使用 Git 进行版本控制&#xff0c;在 gogs 上创建一个个人使用的 git 仓库&#xff1a; http://192.168.…

【Linux】项目自动化构建工具make/makefile的简单使用

使用步骤 1) 编写 创建 makefile 文件 vim makefile用 vim 打开名为 makefile 的文件,存在该文件则打开编辑,不存在则创建并打开.在 makefile 文件中编写需要编译的文件 test:test.cppg -o test test.cpp第一行: 冒号左侧为编译后的可执行文件名,可以随便取. 冒号右侧为依赖…

vue2项目升级到vue3经历分享4

后端重构&#xff0c;如果接口做好抽象封装&#xff0c;只需要考虑jar之间的兼容性问题&#xff0c;jdk版本不变&#xff0c;基本不用做太大的调整&#xff0c;但是前端就不一样&#xff0c;除了vue框架本身&#xff0c;css的调整&#xff0c;改起来更是让人头疼。前面写了vue2…

Linux与windows网络管理

文章目录 一、TCP/IP1.1、TCP/IP概念TCP/IP是什么TCP/IP的作用TCP/IP的特点TCP/IP的工作原理 1.2、TCP/IP网络发展史1.3、OSI网络模型1.4、TCP/IP网络模型1.5、linux中配置网络网络配置文件位置DNS配置文件主机名配置文件常用网络查看命令 1.6、windows中配置网络CMD中网络常用…

C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Mysql 基础 - 常见 子句

算数运算符 > < > < !/<> 逻辑运算符 3i in is null is not null 2l limit like 2o or 、order by 1a and ib between and 1n not and、or 、not、 in、 orderby、 limit、 like、 between...and、 is null 、is not null

我独自升级崛起怎么下载 游戏下载教程分享

《我独自升级&#xff1a;崛起》这款游戏核心聚焦于激烈的战斗与角色的持续成长。新加入的玩家首要任务是熟悉基础攻击模式&#xff0c;随后深入探索技能组合策略与连贯招式的艺术&#xff0c;同时掌握防守与躲避技巧&#xff0c;这些都是战斗中不可或缺的关键。随着战斗的持续…

那个在买珠宝的年轻人

金价搭上过山车&#xff0c;今年以来价格一路飙涨。 珍珠身价同步飙升&#xff0c;晋级珠宝圈“新宠”。 文玩圈“减龄”&#xff0c;盘珠串不再只是“老头乐”。 月薪3000的年轻人&#xff0c;悄悄实现“宝石”自由。 黄金珠宝走俏&#xff0c;这届年轻人到底有着怎样的珠宝…

Baidu Comate智能编码助手 -----AI编程帮你解放双手

目录 Baidu Comate是什么&#xff1f; Baidu Comate如何安装&#xff1f; 在VSCode上安装Baidu Comate插件 Baidu Comate如何使用&#xff0c;有哪些功能&#xff1f; 1.代码解释 2.代码注释 使用感受 如何体验 Baidu Comate是什么&#xff1f; Baidu Comate智能编码助手…

网络编程入门之UDP编程

欢迎各位帅哥美女来捧场&#xff0c;本文是介绍UDP网络编程。在这里&#xff0c;你会见到最详细的教程&#xff1b;细致到每一行代码&#xff0c;每一个api的由来和使用它的目的等。 目录 1.UDP相关API 1.1.两个类 1.2.两个类中的方法 2.UDP编程 2.1.大体框架 2.2.内容构…

修改el-checkbox样式

一定要在最外层&#xff1b; //未选中框/deep/ .el-checkbox__inner{border-color: #0862a3;}//选中框/deep/ .el-checkbox__input.is-checked .el-checkbox__inner{background-color: #0862a3;border-color: #0862a3;}//未选中框时右侧文字/deep/ .el-checkbox__label{}//选中…

企业做网站,如何设计才有创意?

企业做网站&#xff0c;如何设计才有创意&#xff1f;我们都希望能打造一个有创意的网站建设&#xff0c;能在众多网站中脱颖而出&#xff0c;能够营销推广公司的产品&#xff0c;为公司带来更多的经济效益收益。广州网站建设的时候&#xff0c;记住直观的设计可以让用户体验更…

Terrain —— Nodes

目录 Convert HeightField —— 转化高度场 HeightField —— 为地形创建初始高度场或遮罩场 HeightField Blur —— 模糊高度场或遮罩场 HeightField Clip —— 限制高度场的值 HeightField Combine Layers —— 将多个volume或VDB合并为一个新的volume或VDB HeightFiel…

C++浮点数format时的舍入问题

C浮点数format时的舍入问题 首先有这样一段代码&#xff1a; #include <iostream> #include <stdio.h> using namespace std;int main() {cout << " main begin : " << endl;printf("%.0f \r\n", 1.5);printf("%.0f \r\n&…

2024副业指南:年轻人热捧的七大赚钱副业,在家就能做!做得好的月入过万了

副业&#xff0c;听起来就像是在主业之外的“小打小闹”&#xff0c;但你知道吗&#xff1f;很多人通过副业实现了财务自由&#xff0c;甚至有的人副业收入超过了主业&#xff01; 今天&#xff0c;就让我们一起探索那些适合你的副业机会&#xff0c;让你在工作之余也能成为收入…

3D模型素材有哪些常见的用途?

3D模型素材已经成为了设计、游戏开发、电影制作和建筑等领域的重要工具。它们以其独特的形式和丰富的细节&#xff0c;为这些领域的专业人士提供了无尽的创作可能性。 1.建筑和室内设计&#xff1a;在建筑设计中&#xff0c;3D模型可以帮助建筑师更直观地展示设计方案&#xff…
最新文章