leetCode-hot100-二分查找专题

二分查找

    • 简介
    • 原理分析
    • 易错点分析
    • 例题
      • 33.搜索旋转排序数组
      • 34.在排序数组中查找元素的第一个和最后一个位置
      • 35.搜索插入位置
      • 240.搜索二维矩阵 Ⅱ

简介

二分查找,是指在有序(升序/降序)数组查找符合条件的元素,或者确定某个区间左右边界,是一种效率较高的查找方法。

原理分析

必须是有序的序列,否则二分没有效果,数据元素一般是数据型,可以比较大小。
步骤:
(1)初始化lr,确定循环条件(一般为 while(l < r)
(2)计算mid值(一般为mid = (l + r) / 2
数据元素为奇数,例如[1234567]
在这里插入图片描述
数据元素为偶数,例如[123456]
在这里插入图片描述

(3)将目标元素与查找区间的中间值作比较(当二者相等时,查找结束),更新lr的值。
(4)重复第(3)步。

易错点分析

关于mid是否要+1?
有的时候,mid需要+1(如例题34),一般如果l=mid的时候mid需要+1,否则会死循环,l=mid+1的时候mid不需要+1。
关于整数溢出问题
mid = l + (r - l) / 2这个计算方式避免了直接使用(l+ r) / 2可能导致的整数溢出问题。
在二分查找法中,mid = l1 + (r1 - l1) / 2是为了计算当前搜索区间的中间位置。这个计算方式避免了直接使用(l1 + r1) / 2可能导致的整数溢出问题。
解释如下:

  1. lr都很大时,l + r可能会超过整数的最大值,从而导致溢出。
  2. 使用l + (r - l) / 2,首先计算了r - l,这个结果一定小于或等于rl中的较大值,因此不会导致溢出。
  3. 然后将这个差值除以2,得到中间位置与l的偏移量。
  4. 最后将偏移量加到l上,得到中间位置的索引mid
    这种方法确保了即使在lr非常大时,也能正确计算中间位置的索引,而不会因为整数溢出而导致计算错误,这是二分查找中常用的一种技巧,以确保算法的鲁棒性。

关于边界判断,是l < r还是l <= r?以及对应情况下的l和r的更新
在二分查找中,while (l <= r)while (l < r)的选择取决于搜索区间的定义。两种情况下的lr更新方式略有不同,以适应不同的区间定义。

  1. 当使用while (l <= r)时,搜索区间是左闭右闭的,即包含lr。在这种情况下:
    • l的初始值通常是0,表示搜索的起始位置。
    • r的初始值通常是数组的长度减1,表示搜索的结束位置。
    • 在循环中,如果中间元素不等于目标值,需要更新lr来缩小搜索区间。如果中间元素小于目标值,更新l = mid + 1;如果中间元素大于目标值,更新r = mid - 1
    • 循环继续直到l超过r,此时搜索结束。
  2. 当使用while (l < r)时,搜索区间是左闭右开的,即包含l但不包含r。在这种情况下:
    • l的初始值通常是0,表示搜索的起始位置。
    • r的初始值通常是数组的长度,表示搜索的结束位置之后的位置。
    • 在循环中,如果中间元素不等于目标值,同样需要更新lr。如果中间元素小于目标值,更新l = mid + 1;如果中间元素大于目标值,更新r = mid,因为mid位置的元素已经检查过,不需要再次检查。
    • 循环继续直到l等于r,此时搜索结束。

在两种情况下,计算中间位置mid的方式通常是相同的,即mid = l + (r - l) / 2。这样可以避免直接相加lr可能导致的整数溢出问题。
总结来说,while (l <= r)适用于左闭右闭的搜索区间,而while (l < r)适用于左闭右开的搜索区间。在更新lr时,需要根据区间的定义来决定是否包含中间位置mid
但是需要注意的是,这只是二分查找的基础用法,很多题目都需要根据问题进行具体的分析。

例题

33.搜索旋转排序数组

思路
本题采用两次二分查找,第一次查找找到旋转点,从而将数组分为两个部分(因为数组是升序,所以只要比较中间点元素的值和nums[0]的值,如果大于第一个元素的值,说明旋转点在中间点的右侧,更新l值,如果小于,说明旋转点在中间点的左侧,更新r值),这两个部分都是升序的,然后通过确定target和第一个元素的值的大小关系可以确定target在哪一个部分,并更新l或者r的值,再通过二分查找来确定target的下标。视频讲解点击视频讲解-搜索旋转排序数组。
时间复杂度
时间复杂度为O(logn),使用了两次二分查找。
代码实现

class Solution {
    public int search(int[] nums, int target) {
        //二分法查找旋转点
        int l = 0;
        int r = nums.length - 1;
        while(l < r){
            int mid = l + (r - l) / 2 + 1;
            if(nums[mid] >= nums[0]) l = mid;
            else r = mid - 1;
        }

        //确定target的区间
        if(target >= nums[0]) l = 0;
        else{
            l = l + 1;
            r = nums.length - 1;
        } 
        //二分法查找target的下标
        while(l < r){
            int mid = l + (r - l) / 2 + 1;
            if(nums[mid] <= target) l = mid;
            else r = mid -1;
        }
        //这里用r是因为前面有l+1,使用l可能会越界
        return nums[r] == target ? r : -1;
    }
}

注意:二分查找有很多变体,l和r更新的方式不同循环条件也会不同,根据易错点分析中边界判断的规律本题也可以写成下面的形式:

class Solution {
    public int search(int[] nums, int target) {
        int l = 0;
        int r = nums.length - 1;
        //当循环条件变为l <= r时,l的更新方式为l = mid + 1
        //同时mid在计算时不需要+1
        while(l <= r){
            int mid = l + (r - l) / 2;
            if(nums[mid] >= nums[0]) l = mid + 1;
            else r = mid - 1;
        }

        if(target >= nums[0]){
            l = 0;
        }else {
            l = l + 1;
            r = nums.length - 1;
        }
        //当循环条件变为l <= r时,l的更新方式为l = mid + 1
        //同时mid在计算时不需要+1
        while(l <= r){
            int mid = l + (r - l) / 2;
            if(nums[mid] <= target) l = mid + 1;
            else r = mid - 1;
        }

        return nums[r] == target ? r : -1;
    }
}

34.在排序数组中查找元素的第一个和最后一个位置

思路
本题的解题思路和33题目一样,通过两次二分查找来确定左右边界,左边界的查找方法是左边界左边的元素全部小于等于左边界,右边的元素大于等于左边界;有边界的查找方法是右边界左边的元素全部小于等于右边界,右边的元素大于等于右边界,也就是两次二分即可,最后将结果放入List中,视频讲解点击视频讲解-在排序数组中查找元素的第一个和最后一个位置。
时间复杂度
时间复杂度是 O(logn),因为它使用了二分查找的方式来确定左右边界。在最坏的情况下,它需要进行两次二分查找,所以时间复杂度是 O(logn)
代码实现

class Solution {
    public int[] searchRange(int[] nums, int target) {
        //边界判断
        if(nums.length == 0) return new int[] {-1,-1};

        List<Integer> ans = new ArrayList<>(2);
        //确定左边界
        int l1 = 0;
        int r1 = nums.length - 1;
        while(l1 < r1){
            int mid1 = l1 + (r1 - l1) / 2;
            if(nums[mid1] >= target) r1 = mid1;
            else l1 = mid1 + 1;
        }
        if(nums[l1] != target) return new int[] {-1,-1};
        ans.add(l1);

        //确定右边界
        int l2 = 0;
        int r2 = nums.length - 1;
        while(l2 < r2){
            int mid2 = l2 + (r2 - l2) / 2 + 1;
            if(nums[mid2] <= target) l2 = mid2;
            else r2 = mid2 - 1;
        }
        ans.add(r2);
        //将List转为int[]
        int[] result = new int[ans.size()];
        for (int i = 0; i < ans.size(); i++) {
            result[i] = ans.get(i);
        }
        
        return result;
    }
}

其中,最后将List转换为数组除了上面的方法外,还可以用以下的方法:

return ans.stream().mapToInt(Integer::intValue).toArray();

这行代码使用了Java 8的流API来将List<Integer>转换为int[]数组。stream()方法创建一个流,mapToInt(Integer::intValue)将流中的Integer对象映射成int值,最后toArray()方法将流中的元素收集到一个int[]数组中。

分析
该题中的二分查找确实与常规的二分查找算法有所不同,特别是在确定左右边界的时候。这种差异主要是由于目标问题的特殊性:找到数组中等于给定值的元素的第一个和最后一个位置,这种情况下,我们需要对标准的二分查找进行一些调整。
让我们逐步分析代码中的不同之处:

  1. 确定左边界
    • while(l1 < r1):这里使用<而不是<=,是因为我们希望在找到目标值时继续向左搜索,以找到它的第一个出现的位置。如果使用<=,我们可能会在找到目标值后立即停止,这可能会导致我们得到的是目标值的最后一个位置。
    • if(nums[mid1] >= target) r1 = mid1;:如果中间元素大于或等于目标值,我们将右边界移动到中间位置。这是因为我们要找的是左边界,所以我们需要继续在左侧搜索。
  2. 确定右边界
    • while(l2 < r2):同样,这里使用<而不是<=,是因为我们希望在找到目标值时继续向右搜索,以找到它的最后一个出现的位置。
    • int mid2 = l2 + (r2 - l2) / 2 + 1;:这里对中间位置的索引进行了调整,使其向上取整。这是因为在找到目标值时,我们希望继续在右侧搜索,而上取整的中间位置将帮助我们这样做。
    • if(nums[mid2] <= target) l2 = mid2;:如果中间元素小于或等于目标值,我们将左边界移动到中间位置。这是因为我们要找的是右边界,所以我们需要继续在右侧搜索。

这种调整使得我们能够在找到目标值后继续搜索,直到我们找到它的第一个和最后一个位置。在确定了左右边界之后,我们就可以返回目标值的范围了。

35.搜索插入位置

思路:
本题是二分查找比较基础的题目,查找target在数组中的位置,需要注意的是,如果数组中没有target,那么需要返回它的插入位置,所以需要分三种情况来判断返回ll - 1l + 1,需要注意的是当target < nums[l]的时候,直接返回l - 1可能会引起数组越界,需要判断l - 1是否小于0,若小于0,则直接插入到数组首尾,即下标为0的位置,视频讲解点击视频讲解-搜索插入位置。
时间复杂度:
时间复杂度为O(logn)
代码实现:

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

240.搜索二维矩阵 Ⅱ

思路:
从矩阵的右上角开始,比较当前元素与目标值的大小,根据比较结果决定向左走还是向下走,如果目标值小于当前元素则向左走,否则向下走,直到找到目标值或走到矩阵的边界。
时间复杂度:
时间复杂度为O(m+n),其中m为矩阵的行数,n为矩阵的列数,在最坏的情况下,算法会遍历整个矩阵的一行或一列。
代码实现:

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int i = 0;
        int j = matrix[0].length - 1;
        while(i < matrix.length && j >= 0){
            //大于target,向左走;小于target,向右走
            if(matrix[i][j] > target){
                j--;
            }else if(matrix[i][j] < target){
                i++;
            }else{
                return true;
            }
        }
        return false;
    }
}

注意:
二分查找有很多变体,但是思想是不变的,比如易错点分析中总结了两种边界判断和对应lr的更新,实际上可以比较常用的还有以下这种情况:

while(l < r){
	int mid = l + (r - l) + 1;
	if(...) l = mid;
	else r = mid - 1;
}

此时循环结束后lr指向同一个元素,方便后续操作,所以具体问题具体分析,可以自己举个例子走一遍流程,可以更加清楚的掌握某些边界条件应该如何去写。

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

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

相关文章

HTML静态网页成品作业(HTML+CSS)—— 香奈儿香水介绍网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

关于Acrel-2000E配电室综合监控系统的实际应用分析-安科瑞 蒋静

摘要:“三大工程”指的是保障性住房建设、“平急两用”公共基础设施建设、城中村改造&#xff0c;是我国在建设领域作出的重大决策部署&#xff0c;是根据房地产市场新形势推出的重要举措。其中城中村改造是解决群众急难愁盼问题的重大民生工程&#xff0c;该工程中配电房的建设…

新闻发稿:8个新闻媒体推广中最常见的错误-华媒舍

在数字时代&#xff0c;新闻媒体的推广手段已经越来越多样化。许多媒体在推广过程中常常会犯下一些常见错误。本文将会介绍八个新闻媒体在推广中最常见的错误&#xff0c;并希望能够帮助各位更好地规避这些问题。 1. 缺乏明确的目标受众 在进行推广前&#xff0c;新闻媒体需要…

华为OD机试 - 最大坐标值(Java 2024 D卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

将HTML页面中的table表格元素转换为矩形,计算出每个单元格的宽高以及左上角坐标点,输出为json数据

export function huoQuTableElement() {const tableData []; // 存储表格数据的数组let res [];// 获取到包含表格的foreignObject元素const foreignObject document.getElementById(mydctable);if (!foreignObject){return ;}// 获取到表格元素let oldTable foreignObject…

Orange AIpro开箱上手

0.介绍 首先感谢官方给到机会&#xff0c;有幸参加这次活动。 OrangePi AIpro(8T)采用昇腾AI技术路线&#xff0c;具体为4核64位处理器AI处理器&#xff0c;集成图形处理器&#xff0c;支持8TOPS AI算力&#xff0c;拥有8GB/16GB LPDDR4X&#xff0c;可以外接32GB/64GB/128GB/2…

从小众到主流:KOC如何凭借微影响力塑造品牌传播新格局

随着数字化的飞速发展&#xff0c;KOC作为社交媒体上的一股新兴力量&#xff0c;正以其微小的粉丝基数和高度互动性&#xff0c;引发一场微影响力革命。与传统的KOL不同&#xff0c;KOC通常拥有较小的粉丝基数&#xff0c;但却能够凭借高度互动性和真实的消费者体验&#xff0c…

编写一个问卷界面 并用JavaScript来验证表单内容

倘若文章和代码中有任何错误或疑惑&#xff0c;欢迎提出交流哦~ 简单的html和css初始化 今天使用JavaScript来实现对表单输入的验证&#xff0c; 首先写出html代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset&qu…

FY-SA-20237·8-WhyWeSpin

Translated from the Scientific American, July/August 2023 issue. Why We Spin (我们为什么旋转) Primates may play with reality by twirling around 翻译&#xff1a;灵长类动物有能力通过旋转或旋转运动来操纵或扭曲他们对现实的感知。 解释&#xff1a; “Primates”…

跟着大佬学RE(二)

[ACTF新生赛2020]easyre enc~}|{zyxwvutsrqponmlkjihgfedcba_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA?><;:9876543210/.-,*)(\0x27&%$# !" v4*F\"N,\"(I? v4list(map(ord,v4)) print(v4) #( v4[i] ! _data_start__[*((char *)v5 i) - 1] ) flaglist(ACTF…

光猫、路由器的路由模式、桥接模式、拨号上网

下面提到的路由器都是家用路由器 一、联网条件 1.每台电脑、路由器、光猫想要上网&#xff0c;都必须有ip地址。 2.电脑获取ip 可以设置静态ip 或 向DHCP服务器(集成在路由器上) 请求ip 电话线上网时期&#xff0c;猫只负责模拟信号和数字信号的转换&#xff0c;电脑需要使…

折半查找二分查找

简介 折半查找也就是二分查找&#xff0c;也可以叫二分法&#xff0c;本质上都是一样的&#xff0c;通过比对中间值与目标值&#xff0c;一次性就能筛掉一半的数字。 举例&#xff1a; 一个猜数字游戏&#xff0c;让你来猜1-100中我选中的数&#xff0c;如果猜中游戏结束&…

EE trade:量化交易需要什么条件才能做

量化交易结合了金融市场知识和计算机科学技术&#xff0c;利用数学和统计模型来进行交易决策。要成功进行量化交易&#xff0c;需要具备以下几个方面的条件&#xff1a; 1. 知识和技能 金融市场知识&#xff1a;需要理解金融市场的基本原理&#xff0c;包括股票、债券、期货、…

学会读书并不简单,如何真正学会读书

一、教程描述 读书是要讲究方法的&#xff0c;否则就会事倍功半&#xff0c;比如&#xff0c;在学习书本上的每一个问题每一章节的时候&#xff0c;首先应当不只看到书面上&#xff0c;而且还要看到书背后的东西&#xff0c;在对书中每一个问题都经过细嚼慢咽&#xff0c;其次…

AI对话聊天软件有哪些?这5款AI软件值得推荐

AI对话聊天软件有哪些&#xff1f;AI对话聊天软件在现代社会中的重要性日益凸显。它们不仅是沟通的工具&#xff0c;更是人们日常生活中的智能助手。通过深度学习和自然语言处理技术&#xff0c;这些软件能够理解我们的意图&#xff0c;提供个性化的建议和服务&#xff0c;让交…

电生明火电火灶是高科技革命还是营销噱头?

电火灶&#xff0c;一个近年来逐渐进入公众视野的新型厨房烹饪设备&#xff0c;凭借其电生明火的独特技术引起了广泛的讨论和关注。然而&#xff0c;关于其是否真正代表高科技革命&#xff0c;还是仅仅是一个营销噱头&#xff0c;外界众说纷纭。今天&#xff0c;我们就来深度解…

在gitlab上发布npm二进制文件

❝ 允许奇迹发生 ❞ 大家好&#xff0c;我是「柒八九」。一个「专注于前端开发技术/Rust及AI应用知识分享」的Coder。 前言 还记得之前我们讲过如何在 npm 上发布二进制文件&#xff1f;吗。我们通过npm将我们之前在Rust 赋能前端-开发一款属于你的前端脚手架中生成Rust二进制文…

进程通信——管道

什么是进程通信&#xff1f; 进程通信是实现进程间传递数据信息的机制。要实现数据信息传递就要进程间共享资源——内存空间。那么是哪块内存空间呢&#xff1f;进程间是相互独立的&#xff0c;一个进程不可能访问其他进程的内存空间&#xff0c;那么这块空间只能由操作系统提…

私有化部署的无忧企业文档,助力企业实现文档权限的精细化管理

在当今数字化快速发展的时代&#xff0c;企业文档管理已成为企业运营中不可或缺的一部分。文档的安全性和访问权限的精确控制对于企业的信息保护至关重要。在无忧企业文档管理系统中&#xff0c;不仅具备强大的内容管理能力&#xff0c;更在权限管理上做到了细致入微。下面我对…

完全背包(类买卖股票问题)

题目传送门——纪念品 题解&#xff1a;这题我一开始以为是简答的那个买卖股票问题&#xff0c;但是做了之后发现并没有那么简单&#xff0c;但是经过思考时候&#xff0c;我发现其和完全背包类问题差不多&#xff0c;怎么说呢&#xff0c;我们首先用p[i][j]去统计每天每种物品…