【algorithm】算法基础课---二分查找算法(附笔记 | 建议收藏)

news2024/11/15 10:38:13

在这里插入图片描述

🚀write in front🚀
📝个人主页:认真写博客的夏目浅石.
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏:AcWing算法学习笔记
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🖊
✉️如果无聊的话,就来逛逛我的博客栈吧stack-frame.cn

文章目录

  • 前言
  • 一、二分查找的思想
  • 二、二分查找的模板
    • 1.寻找⼀个数(基本的⼆分搜索)
    • 2.边界问题
    • 3.寻找左侧边界的⼆分搜索
    • 4.寻找右侧边界的⼆分查找
  • 三、经典题目集
    • 总结


前言

在这里插入图片描述
关于我写这篇博客的目的以及原因

其实很早前我就写过博客关于二分法,但是我是不满意的或是我觉得不完美的,于是寒假我又花费三天时间又学了一次,今天就把我所学到的经验和知识输出出来,以供复习和学习。
声明:这里知识基于算法小抄深入浅出的程序设计两本书+AcWing算法课(侵权删)


提示:以下是本篇文章正文内容,下面案例可供参考

一、二分查找的思想

由于找一个数遍历的时间复杂度有些题目会超时,所以就需要一个更加优秀的算法—二分查找算法,其实二分算法可以将时间复杂度缩小到logN 想一想为什么?

那么废话不多说,下面就来讲二分查找的基本思想:
我们开始定义两个变量,left,right分别指向数组的左端点和右端点(这里会出现左闭右开以及都是闭区间的边界问题,这个问题下面单独会讲解,大家不用着急)

利用数学上边的二分法就是一次检查一半,这样就可以一次去除一半的不符合要求的数据,大大加大了效率,通过不断地迭代,进而二分出正确答案

二、二分查找的模板

1.寻找⼀个数(基本的⼆分搜索)

这个场景是最简单的,肯能也是⼤家最熟悉的,即搜索⼀个数,如果存在,
返回其索引,否则返回 -1。

在这里插入图片描述
这里再把二分模板的代码附上:
这里是一个左闭右开区间

					//数组 		//目标    //数组长度 
int binarySearch(int* nums, int target, int size)
{
	//特殊情况,可以了解一下这里不计入模板 
	//if(nums==NULL||size==0)
	//	return -1;
	
	int left=0,right=size-1;
	while(left<right)
	{
		int mid=left+(right-left)/2;//防止溢出
		if(nums[mid]>=target) r=mid;
		else l=mid+1;
	}
	if(nums[left]!=target) return -1;
	else return left; 
}

这里是一个左右都闭的方法

					//数组 		//目标    //数组长度 
int binarySearch(int* nums, int target, int size)
{
	//特殊情况,可以了解一下这里不计入模板 
	//if(nums==NULL||size==0)
	//return -1;
	
	int left=0,right=size-1;
	while(left<=right)
	{
		if(nums[mid] == target)
			return mid;
		else if (nums[mid] < target)
			left = mid + 1; // 注意
		else if (nums[mid] > target)
			right = mid - 1; // 注意
	}
	if(nums[left]!=target) return -1;
	else return left; 
}

2.边界问题

1、为什么 while 循环的条件中是 <=,⽽不是 <?
因为初始化 right 的赋值是 size - 1 ,即最后⼀个元素的索
引,⽽不是 size

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

我们这个算法中使⽤的是前者 [left, right] 两端都闭的区间。这个区间
其实就是每次进⾏搜索的区间。

什么时候应该停⽌查找呢?当然,找到了⽬标值的时候可以终⽌:

if(nums[mid] == target)
	return mid;

但如果没找到,就需要 while 循环终⽌,然后返回 -1。那 while 循环什么时
候应该终⽌?查找区间为空的时候应该终⽌,意味着你没得找了,就等于没
找到嘛。

while(left <= right) 的终⽌条件是 left == right + 1 ,写成区间的形式
就是 [right + 1, right] ,或者带个具体的数字进去 [3, 2] ,可⻅这时候
区间为空,因为没有数字既⼤于等于 3 ⼜⼩于等于 2 的吧。所以这时候
⼆分查找解题套路框架
while 循环终⽌是正确的,直接返回 -1 即可。
while(left < right) 的终⽌条件是 left == right ,写成区间的形式就是
[left, right] ,或者带个具体的数字进去 [2, 2] ,这时候区间⾮空,还
有⼀个数 2,但此时 while 循环终⽌了。也就是说这区间 [2, 2] 被漏掉
了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

当然,如果你⾮要⽤ while(left < right) 也可以,我们已经知道了出错的
原因,就打个补丁好了:

//...
while(left < right) {
// ...
}
return nums[left] == target ? left : -1;

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

这也是⼆分查找的⼀个难点,不过只要你能理解前⾯的内容,就能够很
容易判断。

本算法的查找区间是两端都闭的,
即 [left, right] 。那么当我们发现索引 mid 不是要找的 target 时,下
⼀步应该去搜索哪⾥呢?

当然是去搜索 [left, mid-1] 或者 [mid+1, right] 对不对?因为 mid 已
经搜索过,应该从搜索区间中去除。

3、此算法有什么缺陷?
我想你应该已经掌握了该算法的所有细节,以及这样处理的原因。但
是,这个算法存在局限性。
⽐如说给你有序数组 nums = [1,2,2,2,3] , target 为 2,此算法返回的索
引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我
想得到 target 的右侧边界,即索引 3,这样的话此算法是⽆法处理的。
⼆分查找解题套路框架

这样的需求很常⻅,你也许会说,找到⼀个 target,然后向左或向右线性搜
索不⾏吗?可以,但是不好,因为这样难以保证⼆分查找对数级的复杂度
了。

我们后续的算法就来讨论这两种⼆分查找的算法。

3.寻找左侧边界的⼆分搜索

					//数组 		//目标    //数组长度 
int binarySearch(int* nums, int target, int size)
{
	//特殊情况,可以了解一下这里不计入模板 
	//if(nums==NULL||size==0)
	//	return -1;
	
	int left=0,right=size;//注意
	while(left<right)
	{
		int mid=left+(right-left)/2;//防止溢出
		if(nums[mid]>=target) r=mid;
		else l=mid+1;
	}
	if(nums[left]!=target) return -1;
	else return left; 
}

1、为什么 while 中是 < ⽽不是 <= ?
⽤相同的⽅法分析,因为 right = size ⽽不是 size - 1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。
while(left < right) 终⽌的条件是 left == right ,此时搜索区间 [left, left) 为空,所以可以正确终⽌。

PS:这⾥先要说⼀个搜索左右边界和上⾯这个算法的⼀个区别,也是很多
读者问的:刚才的 right 不是 size - 1 吗,为啥这⾥⾮要写成
size 使得「搜索区间」变成左闭右开呢?

因为对于搜索左右侧边界的⼆分查找,这种写法⽐较普遍,我就拿这种写法
举例了,保证你以后遇到这类代码可以理解。你⾮要⽤两端都闭的写法反⽽
更简单,我会在后⾯写相关的代码,把三种⼆分搜索都⽤⼀种两端都闭的写
法统⼀起来,你耐⼼往后看就⾏了。

2、为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎
么办?

因为要⼀步⼀步来,先理解⼀下这个「左侧边界」有什么特殊含义:
在这里插入图片描述
对于这个数组,算法会返回 1。这个 1 的含义可以这样解读: nums 中⼩于
2 的元素有 1 个。

⽐如对于有序数组 nums = [2,3,5,7] , target = 1 ,算法会返回 0,含义
是: nums 中⼩于 1 的元素有 0 个。
再⽐如说 nums = [2,3,5,7], target = 8 ,算法会返回 4,含义是: nums
中⼩于 8 的元素有 4 个。
⼆分查找解题套路框架

综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间
[0, size] ,所以我们简单添加两⾏代码就能在正确的时候 return
-1;

3、为什么 left = mid + 1 , right = mid ?和之前的算法不⼀样?
这个很好解释,因为我们的「搜索区间」是 [left, right) 左闭右
开,所以当 nums[mid] 被检测之后,下⼀步的搜索区间应该去掉 mid 分
割成两个区间,即 [left, mid)[mid + 1, right)

4、为什么返回 left ⽽不是 right ?
都是⼀样的,因为 while 终⽌的条件是 left == right 。

int left_bound(int[] nums, int target)
{
	int left = 0, right = nums.length - 1;
	// 搜索区间为 [left, right]
	while (left <= right)
	{	
		int mid = left + (right - left) / 2;
		if (nums[mid] < target) {
		// 搜索区间变为 [mid+1, right]
		left = mid + 1;
		}
		else if (nums[mid] > target) 
		{
			// 搜索区间变为 [left, mid-1]
			right = mid - 1;
		} 
		else if (nums[mid] == target) 
		{
			// 收缩右侧边界
			right = mid - 1;
		}
	}
}
// 检查出界情况
	if (left >= nums.length || nums[left] != target)
		return -1;
	return left;
}

4.寻找右侧边界的⼆分查找

int right_bound(int[] nums, int target)
{
	int left = 0, right = nums.length - 1;
	// 搜索区间为 [left, right]
	while (left <= right)
	{	
		int mid = left + (right - left) / 2;
		if (nums[mid] < target) {
		// 搜索区间变为 [mid+1, right]
		left = mid + 1;
		}
		else if (nums[mid] > target) 
		{
			// 搜索区间变为 [left, mid-1]
			right = mid - 1;
		} 
		else if (nums[mid] == target) 
		{
			// 收缩右侧边界
			left = mid - 1;
		}
	}
}
// 检查出界情况
	if (lright < 0 || nums[right] != target)
		return -1;
	return right;
}

思路类似左边界。

三、经典题目集

在这里插入图片描述

int search(int* nums, int numsSize, int target)
{
    int left=0,right=numsSize-1;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        if(nums[mid]==target)
        {
            return mid;
        }
        else if(nums[mid]>target)
        {
            right=mid-1;
        }
        else if(nums[mid]<target)
        {
            left=mid+1;
        }
    }
    return -1;
}
// int search(int* nums, int numsSize, int target)
// {
//     int left=0,right=numsSize-1;
//     while(left<right)
//     {
//         int mid=(right+left)/2;
//         if(nums[mid]>=target) right=mid;
//         else left=mid+1;
//     }
//     if(nums[left]!=target) return -1;
//     else return left;
// }

在这里插入图片描述

int searchInsert(int* nums, int numsSize, int target)
{
    int left=0,right=numsSize-1,ans=numsSize;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        if(nums[mid]>=target)
        {
            ans=mid;
            right=mid-1;
        }
        else left=mid+1;
    }
    return ans;
}


// {
//     int left=0,right=numsSize;
//     while(left<right)
//     {
//         int mid=(left+right)/2;
//         if(nums[mid]>=target) right=mid;
//         else left=mid+1;
//     }
//     return left;
// }

在这里插入图片描述

/*
 * 输入 **matrix 是长度为 matrixSize 的数组指针的数组,其中每个元素(也是一个数组)
 * 的长度组成 *matrixColSize 数组作为另一输入,*matrixColSize 数组的长度也为 matrixSize
 */                     //二维数组         //数组长度         //一维数组       //目标数字
bool findNumberIn2DArray(int** matrix, int matrixSize, int* matrixColSize, int target)
{
    if(matrix==NULL || matrixSize==0 || *matrixColSize==0)
        return false;
    
    int row=matrixSize; //行数
    int col=matrixColSize[0]; 
    
    int i=0;
    int j=col-1;
    while(i<row && j>=0)
    {
        if(matrix[i][j]==target) return true;
        else if(matrix[i][j]>target) j--;
        else if(matrix[i][j]<target) i++;
    }

    return false;
}

总结

1、分析⼆分查找代码时,不要出现 else,全部展开成 else if ⽅便理解。
2、注意「搜索区间」和 while 的终⽌条件,如果存在漏掉的元素,记得在
最后检查。
3、如需定义左闭右开的「搜索区间」搜索左右边界,只要在 nums[mid] == target 时做修改即可,搜索右侧时需要减⼀。
4、如果将「搜索区间」全都统⼀成两端都闭,好记,只要稍改 nums[mid] ==target 条件处的代码和返回的逻辑即可,推荐拿⼩本本记下,作为⼆分搜索模板。

  我是夏目浅石,希望和你一起学习进步,刷题无数!!!希望各位大佬能一键三连支持一下博主,hhhh~我们下期见喽

特别注意:本次博客基于算法小抄以及AcWing算法课写出来的内容如果想进一步学习,希望您可以自己看书+看视频。

在这里插入图片描述

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

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

相关文章

几种常用的权重初始化方法

来源&#xff1a;投稿 作者&#xff1a;175 编辑&#xff1a;学姐 在深度学习中&#xff0c;权重的初始值非常重要&#xff0c;权重初始化方法甚至关系到模型能否收敛。本文主要介绍两种权重初始化方法。 为什么需要随机初始值 我们知道&#xff0c;神经网络一般在初始化权重…

【EasyExcel】在Java中操作Excel 完成数据的导入导出

快速入门 引入依赖 构建实体类 数据导出 参数 WriteWorkbook WriteSheet WriteTable 测试 数据导入 测试 EasyExcel是阿里巴巴开源的一个excel处理框架&#xff0c;以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一…

【Ajax】form表单

一、form表单的基本使用什么是表单表单在网页中主要负责数据采集功能。HTML中的<form>标签&#xff0c;就是用于采集用户输入的信息&#xff0c;并通过<form>标签的提交操作&#xff0c;把采集到的信息提交到服务器端进行处理。2. 表单的组成部分<!-- 表单标签 …

Android 深入系统完全讲解(27)

讲完了这块&#xff0c;我们来说下相机相关的&#xff0c;再说之前一定记得&#xff0c;先要有框架思维&#xff0c;这点一直是我 强调的。 相机是什么&#xff0c;硬件采集数据上来&#xff0c;解析完成&#xff0c;上层绘制&#xff0c;在绘制的时候&#xff0c;同步可以做特…

iOS 国际化(多语言)

一、应用程序国际化 包括app名称和各种权限的提示文字。 1.1 创建工程&#xff0c;再在“PROJECT”的“Info”里面&#xff0c;添加所需语言。 1.2 从代码中分离出文本 创建一个 “.strings” 扩展名的文件 来本地化字符串&#xff0c;需要把这些字符串全部放在一个单独的文…

【医学数据融合文本方向 思路整理】

Scalable and accurate deep learning for electronic health records【2018】 本论文在于介绍 Google Medical Brain 项目的目标、方法和规划。 思路&#xff1a; 用病情描述&#xff0c;预测疾病诊断&#xff0c;预测死亡率 用病情描述加治疗方案&#xff0c;预测复诊率和住院…

Elasticsearch7.8.0版本高级查询—— 查询所有文档

目录一、初始化文档数据二、查询所有文档示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; {"name":"张三","age":22,"sex…

Zookeeper 【下载与安装,基本使用】

目录 1. 什么是zookeeper 2. zookeeper下载与安装 3. Zookeeper 测试 1. 什么是zookeeper zookeeper实际上是yahoo开发的&#xff0c;用于分布式中一致性处理的框架。最初其作为研发Hadoop时的副产品。 由于分布式系统中一致性处理较为困难&#xff0c;其他的分布式系统没有…

SAP MTO/MTS操作步骤及月末结算

一、MTO/MTS操作步骤 【MTO核算方式】 是以销售订单触发生产的方式。 创建销售订单 VA01 运行物料需求计划 MD01 查询物料需求 MD04 计划订单转化为生产订单 MD04/CO01 生产订单成本计算以及下达 CO02 生产订单发料 MB1A 生产报工 CO11N 完成品入库 MB31 非限制库存转移到销售…

C# opencv多模板匹配实战应用例程

C# 多模板匹配例程 最近在做项目的时候为了检测某一种物品的齐套性&#xff0c;以及为了和写c#的软件负责人配合自己研究了一下opnencv C# 版的模板匹配&#xff0c;对基础的例程做了一下改进&#xff0c;留一份例程。 因为工作性质原因不能直接放项目的实际图片我用visio简单…

我的个人微信也迅速接入了 ChatGPT

本文主要来聊聊如何快速使用个人微信接入 ChatGPT&#xff0c;欢迎 xdm 尝试起来&#xff0c;仅供学习参考&#xff0c;切莫用于做不正当的事情 关于 ChatGPT 我们每个人都可以简单的使用上&#xff0c;不需要你有很强的技术背景&#xff0c;不需要你有公众号&#xff0c;甚至…

Chat GPT 创建APP: 开发人员要被替代了吗?

我们又要被人工智能取代了吗&#xff1f;GitHub Copilot 于 2021 年 10 月发布&#xff0c;整个开发社区都为之疯狂。有些人发表言论说我们很快就会失业&#xff0c;而其他人&#xff08;比如我&#xff09;&#xff0c;认为虽然这个工具很有趣&#xff0c;但距离替代人工还很远…

【Django框架】——25 Django视图 07 状态保持Session

文章目录1.session流程图2.session语法与案例3.session配置cookie不安全&#xff0c;会把所有敏感数据放到浏览器保存。 session是把敏感数据存到自己的服务器中给浏览器一把钥匙就行了&#xff08;是基于cookie完成的&#xff09;。 Django 提供对匿名会话(session)的完全支…

Cisco Packet Tracer 8.2.x Crack

Cisco Packet Tracer 是一个网络模拟器。有了这款功能强大的软件&#xff0c;用户可以在模拟和安全的环境中学习所有网络主题&#xff0c;而无需花费很多钱。它是网络主题模拟和培训领域中最受欢迎的应用程序之一&#xff0c;因为它提供了这样做所需的所有功能。Packet Tricer …

Java方法(函数)

文章目录Java方法(函数)一、方法介绍二、方法的定义和调用格式1. 快速入门2. Debug查看方法的执行流程3. 方法调用内存图解4. 带参数方法的定义和调用1&#xff09;定义和调用格式2&#xff09;形参和实参5. 带返回值方法的定义和调用6. 方法通用定义格式三、方法常见问题四、方…

MIPI 摄像头的原理

1. 摄像头sensor 的原理 定时脉冲生成器会生成clock&#xff0c;用于访问image sensor 阵列中的行&#xff0c;预充电&#xff0c;并且按顺序采样像素阵列中的所有行。在一个行的预充电和采样的时间段里&#xff0c;像素的电荷量会随着曝光时间而逐渐减少。这就是快门结构中的曝…

擎创技术流 | ClickHouse实用工具—ckman教程(10)

一、前言 哈喽~友友们&#xff0c;转眼农历新年就在眼前&#xff0c;ckman系列也终于迎来了最后一期&#xff0c;非常感谢大家的喜欢&#xff0c;让up主有动力做完这个系列&#xff0c;也感谢一路走来&#xff0c;大家给予的反馈&#xff0c;让这个系列越做越好。 接下来&…

4-Spring使用

目录 1.存储Bean对象到Spring容器中 1.1.创建Bean 1.2.将Bean注册到Spring容器中 1.2.1.第一次存储Bean&#xff08;可选&#xff0c;如果是第二次及以后&#xff0c;此步骤忽略&#xff09; 1.2.2.添加Bean标签 2.从Spring容器中获取并使用Bean对象 2.1.创建Spring上下…

剑指 Offer 04. 二维数组中的查找 [C语言]

目录题目思路代码结果该文章只是用于记录考研复试刷题题目 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右 非递减 的顺序排序&#xff0c;每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff…

[leetcode 1723] 完成所有工作的最短时间

题目 题目&#xff1a;https://leetcode.cn/problems/find-minimum-time-to-finish-all-jobs/description/ 该题和 [leetcode 2305] 公平分发饼干 完全相同。 解法 回溯剪枝 感觉和 [leetcode 198] 划分为k个相等的子集 有点相似&#xff0c;这题更像是划分为k个尽量相等的子…