算法【Java】—— 二分查找

news2024/11/15 17:51:56

二分查找算法分析

二分查找算法其实也是对撞指针的另一种用法,左右两个指针分别指向数据的左右端点,然后双指针向中间移动。

朴素二分查找

在这里插入图片描述

上面这道题是朴素的二分查找算法,由于数据是有序的,我们可以从中间值入手
在这里插入图片描述
如果中间值大于目标值说明目标值位于绿色区间则需要修改右指针,如果中间值小于目标值的那说明目标值位于蓝色区间则需要修改左指针,如果相等的话直接返回下标即可。

这里的 mid = left + (right - left) / 2 是为了防止溢出,大家应该会这样写 mid = (left + right) / 2,但是由于整型数据是有范围的,所以直接加的话可能会出现溢出现象,为了避免这一现象的出现,我们使用 left + (right - left) / 2,利用减法获取一半。

补充: mid = left + (right - left) / 2 或者 mid = left + (right - left + 1) / 2 ,在朴素的二分查找算法是一样的。

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

左右端点二分查找

在这里插入图片描述

非递减顺序排列指的是可以是递增,也可以存在相同是数据,例如:【1,2,3,3,4,4,4,5,5,6,7】

我们要找到 target 的左端点和右端点,并且要求时间复杂度是 logN,大概率要使用二分查找算法来做,那现在我们需要考虑怎么二分,首先target 的左端点就分成大于等于target 这个区间和 小于target 这个区间,target 的右端点就分成 小于等于 target 这个区间和 大于 target 这个区间。

算法实现:
循环条件:left < right

为什么不加上等于?因为加上等于的话,根据上面我们的二分条件可以知道在循环中我们最后会让 left == right,这个时候就是二者相遇,并且可能会出现 nums[left] == taregt 又或者不相等,如果是相等的话,就会卡死在循环里,所以循环条件不能加等于

算法内部条件细节:
左端点: if(nums[mid] >= target) 这个需要 right = mid ,为什么不能是 right = mid - 1 呢? 因为mid 所对应的数据可能就是 target ,如果是那就不能跳过了,如果不是那就需要跳过,所以当 nums[mid] < target 时,要left = mid + 1;

右端点也是和上面类似的,if(nums[mid] >= target)这个条件出现的时候,说明 mid 可能对应的就是target , 所以不能跳过,left = mid; 否则就是 nums[mid] > target,就需要right = mid - 1

mid 的处理:
mid = left + (right - left) / 2 或者 mid = left + (right - left + 1) / 2 在这里就是不一样的,我们要根据实际情况分析,如果是查找左端点,就要使用 mid = left + (right - left) / 2,right 就会移动到 left ,否则 right 就不会移动,这时候就是死亡循环了,右端点也是同理可得的。
在这里插入图片描述

本题细节处理:
如果数组长度为零,需要单独讨论,避免数组越界访问。
在第一个二分左端点的时候,需要判断此时的left 对应的数据是不是 target ,如果不是,说明不存在target ,直接返回答案,如果是,则要修改答案数组,既然存在target ,就是可以进行右端点的查找,并且此时一定存在右端点

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ans = {-1,-1};
        if(nums.length == 0) {
            return ans;
        }
        int left = 0;
        int right = nums.length - 1;
        //二分左端点
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        if(nums[left] != target) {
            return ans;
        }
        ans[0] = left;
        right = nums.length - 1;

        //二分右端点
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }

        ans[1] = left;
        return ans;
    }
}

小结

当数据具有二段性的时候,我们可以使用二分查找算法来查找目标的数据。

朴素二分算法是最基础的,一般要使用到二分的算法题基本用到的是左右端点的二分算法思路。

左右端点二查找算法的模板:

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

mid 的取值:可以这样子记忆,如果判断条件出现 -1 ,说明 mid 就要 +1,即 mid = left + (right - left + 1) / 2

实战演练

使用二分查找算法的时候,一定要找到二段性,只要找到了,一切都好办,剩下的就是套模板。

搜索插入位置

在这里插入图片描述

二段性:小于 target ,大于等于 target
最后有三种情况,要么是找到了 target ,直接返回下标,如果没有找到,这时候此时的下标对应的数据要么大于target 要么小于 target ,如果是大于target 也是直接返回下标,因为是插入,如果是小于,那就要返回下标加一。

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

x 的平方根

在这里插入图片描述

暴力算法是枚举所有的数字,但是这里我们可以使用二分查找算法,当 中间值的平方和大于 target ,说明中间值和大于中间值的数据都是不符合的,如果中间值是小于的话,那就说明中间值以及小于中间值的数据都是不符合的,符合二段性

这里建议使用左右端点的算法思路,这是万能的,接下来就是等于放在哪个条件,这个交给你们,都是没有问题的。

最后要注意溢出问题,因为是通过平方来比较,所以很有可能会出现溢出,这里强制类型转化一下即可。

class Solution {
    public int mySqrt(int x) {
        int left = 0;
        int right = x;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if((long)mid * mid >= x) {
                right = mid;
            } else if((long)mid * mid < x) {
                left = mid + 1;
            }
        }
        if(left * left == x) {
            return left;
        }
        return left - 1;
    }
}

山脉数组的峰顶索引

在这里插入图片描述

二段性超明显,可能大家目前不知道怎么写代码,不过大家对山脉那可以了如指掌,类似一个三角形,只要进行二分找到峰顶即可。

利用 arr[mid] 前一个数据或者后一个数据进行比较就可以了。

最后二段性自然也就出来了。

class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(arr[mid] > arr[mid - 1]) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
}

寻找峰值

在这里插入图片描述

这个和上面的山脉数组的峰顶索引一模一样,这里就交给聪明的大家了。

class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] > nums[mid - 1]) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
}

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

在这里插入图片描述

将军莫虑,且看此图:
在这里插入图片描述

因为是有序的数组,经过旋转之后,最小值的左边一定都大于它,最小值的右边同样也都大于它,但是有一个特殊的地方,就是最小值的右区间的最小值是比有区间的最大值要大的,左右区间的最值很好找,就是端点对应的数值。

二段性:
nums[left] > nums[right] 时,left 要 + 1 ,避免跳过了最小值,相反则是 nums[left] <= nums[right],这时候说明left 对应的就是最小的元素,直接返回即可。

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while(left < right) {
            if(nums[left] > nums[right]) {
                left++;
            } else {
                return nums[left];
            }
        }
        return nums[left];
    }
}

点名

在这里插入图片描述

这里可以使用暴力美学,比较简单

如果要使用二分查找,就需要利用好数组自身内容和下标,如果发现是缺人的话,下标和数组自身内容是不匹配的:
在这里插入图片描述

那么二段性也就出来了,首先 mid == nums[mid] 的话,需要将 left 移动到 mid + 1,然后继续二分
如果 mid != nums[mid] 的话,需要将 right 移动到 mid ,但不能是 mid - 1 ,可能mid 就是答案。
综上所述,使用的是 左右端点二分查找算法的模板,大家直接套模板即可。

现在讨论特殊情况,如果刚好缺的是最后一个学号,这时候,经过二分查找之后,会有两种情况,一种是刚好下标等于数组内容,那么缺席的就是 right + 1 这个学号,另一种是下标不等于数组内容,这种直接返回下标即可。

class Solution {
    public int takeAttendance(int[] records) {
        int left = 0;
        int right = records.length - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(mid != records[mid]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        if(right == records.length - 1 && records[right] == right) {
            return right + 1;
        }
        return right;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国货之光|暴雨机推出面向大模型训练的AI服务器

当前&#xff0c;“百模大战”带来了算力需求的爆发&#xff0c;尤其是以ChatGPT为代表的多模态AI大模型&#xff0c;‌其参数规模和训练数据量均达到了前所未有的规模。‌GPT-3.5、‌GPT-4等模型的参数规模突破了万亿级别&#xff0c;‌训练数据量达到了TB级别&#xff0c;‌这…

AI多模态模型架构之模态生成器:Modality Generator

〔探索AI的无限可能&#xff0c;微信关注“AIGCmagic”公众号&#xff0c;让AIGC科技点亮生活〕 本文作者&#xff1a;AIGCmagic社区 刘一手 前言 AI多模态大模型发展至今&#xff0c;每年都有非常优秀的工作产出&#xff0c;按照当前模型设计思路&#xff0c;多模态大模型的…

Ai+若依(定时任务):【01篇】

若依为定时任务功能提供方便友好的web界面,实现动态管理任务。 案例 每间隔5秒,控制台输出系统时间。 实现步骤: ①创建任务类 在服务 ==》ruoyi-quartz==》task==》MyTask package com.ruoyi.quartz.task;import org.springframework.stereotype.Component;@Component…

程序员修炼之道 10:并发

不记录&#xff0c;等于没读。 这里是我阅读《程序员修炼之道》这本书的记录和思考。 并发和并行代码曾经是很新奇的玩意&#xff0c;但现在已经是标配。 并发 &#xff1a;Concurrency&#xff0c;指的是两个或更多个代码段在执行过程中表现得像是在同时运行一样。 并行 &am…

AD的3D模型格式是什么

AD通常指的是Altium Designer&#xff0c;这是一款用于电子设计自动化的软件&#xff0c;主要用于电路板的设计。在Altium Designer中&#xff0c;3D模型主要用于PCB&#xff08;印制电路板&#xff09;设计中的可视化&#xff0c;以便设计师能够在三维空间中查看组件和板的布局…

21:【stm32】定时器二:输入捕获

输出捕获 1、输出捕获的简介2、输入捕获原理2.1、输入滤波2.2、信号选择2.3、CCx事件和CCx中断 3、标准库编程 1、输出捕获的简介 定时器通过通道捕获输入电平的变化&#xff0c;而通道有CH1~CH4&#xff0c;这4个通道。 我们设定对通道CH3的输入电平进行捕获。假如CH3突然捕获…

Java排序算法详解

在Java编程中&#xff0c;排序算法是数据处理和算法设计中的基础且重要的部分。它们广泛应用于各种场景&#xff0c;如数据库管理、数据分析、图形界面排序等。掌握几种常见的排序算法对于提升程序效率和优化性能至关重要。本文将详细解析几种经典的Java排序算法&#xff0c;包…

OpenGL笔记二十之深度检测概念

OpenGL笔记二十之深度检测概念 —— 2024-08-25 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记二十之深度检测概念1.课程PPT截图2.运行3.代码 1.课程PPT截图 2.运行 3.代码 关键部分 main.cpp #include <iostream>#include "glfra…

【逐行注释】三维EKF的MATLAB代码|附下载链接

文章目录 程序概况源代码与注释运行结果代码块解析 程序概况 基于MATLAB的EKF&#xff08;扩展卡尔曼滤波&#xff09;代码解析。状态转移和观测都是非线性的&#xff0c;也就是说会涉及到雅克比矩阵的求解。我尽量将模型设计复杂一点&#xff0c;便于拿到手以后改成自己想要的…

C++类和对象(2)——拷贝构造函数

拷贝构造函数的语法 拷贝构造函数是构造函数的重载&#xff0c; 用于这种情况&#xff1a;用已经构造好的对象去给另一个对象初始化。 int main() {Date d1(2024, 8, 1);Date d2(d1);//用d1初始化d2return 0; } 我们以Date类为例子讲解一下。 class Date { public://全缺省…

.NET周刊【8月第3期 2024-08-18】

国内文章 Roslyn 简单实现代码智能提示补全功能 https://www.cnblogs.com/lindexi/p/18365261 相信有很多伙伴热衷于编写 IDE 应用&#xff0c;在 dotnet 系下&#xff0c;通过 Roslyn 友好的 API 和强大的能力&#xff0c;实现一个代码智能提示是非常简单的事情。本文将和大…

【互动直播】支付能力视角与年龄的调节作用—推文分享—2024-08-25

【今天的关键词是&#xff1a;“interaction” AND “live commerce”】 第一篇&#xff1a;通过信息技术的可供性&#xff08;IT affordance&#xff09;视角&#xff0c;直播电商的实用、社交和享乐三个维度的IT可供性如何影响购买意愿&#xff1b; 第二篇&#xff1a;研究…

凭借着“16连射”造就一个游戏角色的诞生

一、高桥名人的冒险岛起源 大约在 1986 年&#xff0c;Hudson 的所有人都很喜欢一款名为《神奇男孩》的街机游戏&#xff0c;公司内部有人建议将其改编成 Famicom 游戏。因此&#xff0c;副总裁和高桥名人去查看角色和游戏的开发情况&#xff0c;当他们查看正在构建的角色时&am…

C++类和对象的基础介绍(1)

类的定义 C类的关键字是class&#xff0c;学过c的朋友们可能会觉得和结构体struct有点像。 在C中struct也可以定义类&#xff0c;但和class定义的有所区别。 我们还是推荐使用class。 类里面会有成员变量、成员函数的定义和声明、构造函数、拷贝构造函数、析构函数、运算符…

迈巴赫S480升级大柏林音响31个喇叭8个座椅震动加上后排按摩座椅更加舒适

迈巴赫 S480 改装大柏林音响的改装案例&#xff1a; 改装前的车辆音响系统&#xff1a; 迈巴赫 S480 原车配备了 15 个喇叭的小柏林音响系统。 改装方案&#xff1a; 1. 扬声器升级&#xff1a;将原车的小柏林音响全部拆下&#xff0c;更换为大柏林的扬声器。大柏林音响系统…

SCMsafe链四方参展2024第三届医药供应链创新大会

024年8月22日-24日&#xff0c;由智慧医药主办&#xff0c;多家行业协会、研究机构、生产、流通及供应链公司协办的2024第三届医药供应链创新大会在杭州召开。 会议以“正在遇见的革命”为主题&#xff0c;聚焦生产、流通及终端医药供应链热点、痛点政策和市场话题&#xff0c;…

opencv实战项目十五:钢材表面缺陷检测

文章目录 前言一、算法实现流程&#xff1a;二&#xff0c;算法详解&#xff1a;2.1 二值化与去噪2.2 形态学处理 三&#xff0c;整体代码实现&#xff1a; 前言 随着科技的不断进步&#xff0c;自动化和智能化在工业生产中的应用越来越广泛。在钢材生产过程中&#xff0c;钢材…

认知杂谈33

今天分享 有人说的一段争议性的话 I I 《说话影响命运&#xff0c;得小心》 嘿&#xff0c;你想想看&#xff0c;咱平常不经意说的那些话&#xff0c;就像小种子一样。你可能没当回事&#xff0c;可说不定啥时候&#xff0c;这些话就会在生活里生根发芽&#xff0c;最后生活…

继承—构造函数—引用等

继承时&#xff0c;数据成员&#xff0c;函数成员全盘接收&#xff0c;如果碰见同名成员屏蔽基类成员。 1&#xff0c;无论采取什么继承方式&#xff0c;基类中所有数据成员都将继承到派生类 2&#xff0c;在类型的继承层次里&#xff0c;保护属性当作共有属性使用 3&#x…

Xv6驱动(一):PLIC

PLIC内存布局 #define PLIC 0x0c000000L #define PLIC_PRIORITY (PLIC 0x0) #define PLIC_PENDING (PLIC 0x1000) #define PLIC_SENABLE(hart) (PLIC 0x2080 (hart) * 0x100) #define PLIC_SPRIORITY(hart) (PLIC 0x201000 (hart) * 0x2000) #define PLIC_SCLAIM(hart) …