详解二分查找的两种写法以及二分查找的六种变形

news2025/1/10 20:25:54

目录

一、二分查找的两种写法

1.1 - 第一种写法(左闭右闭)

1.2 - 第二种写法(左闭右开)

二、二分查找的六种变形

2.1 - 查找第一个 = target 的元素位置

2.2 - 查找第一个 >= target 的元素位置

2.3 - 查找第一个 > target 的元素位置

2.4 - 查找最后一个 = target 的元素位置

2.5 - 查找最后一个 <= target 的元素位置

2.6 - 查找最后一个 < target 的元素位置

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



一、二分查找的两种写法

二分查找也成为折半查找(Binary Search),它是一种效率较高的查找方法。但是该方法要求待查找的序列必须是有序的,即序列中所有的元素都是按照升序(递增)或降序(递减)排列的

二分查找的思想很简单,即选择序列中间的数字和目标值进行比较(假设序列是按升序排列的):

  1. 如果中间的数字小于目标值,说明包括中间数字在内的左半边区间的所有数字都小于目标值,可以全部排除。

  2. 如果中间的数字大于目标值,说明包括中间数字在内的右半边区间的所有数字都大于目标值,可以全部排除。

  3. 如果中间的数字等于目标值,则直接返回答案。

练习:704. 二分查找。

1.1 - 第一种写法(左闭右闭)

左闭右闭,即每次查找的区间为 [left, right]

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

1.2 - 第二种写法(左闭右开)

左闭右开,即每次查找的区间为 [left, right),此时 arr[right] 不存在或者不符合条件。因此写法二的代码要做如下几处的修改:

  1. right 要初始化为 size

  2. while 的循环条件应该改为 left < right

  3. arr[mid] > target 时,right = mid

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


二、二分查找的六种变形

2.1 - 查找第一个 = target 的元素位置

此时待查找的序列是按照非递减或者按非递增的顺序排列的,即序列中可能有重复的数字

因此当查找序列中第一个等于 target 的元素位置时(假设序列按非递减的顺序排列),arr[mid] == target,也要让 right = mid - 1

当 while 循环结束以后,判断 left 是否越界以及 arr[left] 是否等于 target,因为序列中所有的元素可能都小于 target,或者序列中并不存在等于 target 的元素。

如果 left 既没有越界,arr[left] 又等于 target,则 left 就是第一个等于 target 的元素位置。

理解方式一

此时 arr[right] <= target

  1. arr[right] < target,则说明 mid,即 right + 1 就是第一个 = target 的元素位置,当 while 循环结束以后,left 等于 mid

  2. arr[right] == target,则说明 mid 不是第一个 = target 的元素位置,而可能是当前的 right 或者是更之前的位置。

理解方式二

当 while 循环结束以后(left > right):

  • left 左边的元素都小于 target。

  • right 右边的元素都大于或等于 target。

所以此时 right + 1,即 left 就可能是第一个等于 target 的元素位置(因为也有可能是大于 target 的元素位置)。

int firstEq(int arr[], int size, int target)  // Eq:Equal to
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] < target)
		{
			left = mid + 1;
		}
		else  // arr[mid] >= target
		{
			right = mid - 1;
		}
	}
	if (left < size && arr[left] == target)
		return left;
	return -1;
}

 

2.2 - 查找第一个 >= target 的元素位置

和查找第一个等于 target 的元素位置不同在于,当 while 循环结束以后不需要判断 left 是否越界以及 arr[left] 是否等于 target,如果序列中所有的元素都比 target 小,则返回序列的长度

int firstGE(int arr[], int size, int target)  // GE:Greater than or Equal to
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] < target)
		{
			left = mid + 1;
		}
		else  // arr[mid] >= target
		{
			right = mid - 1;
		}
	}
	return left;
}

2.3 - 查找第一个 > target 的元素位置

int firstGt(int arr[], int size, int target)  // Gt:Greater than
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] <= target)
		{
			left = mid + 1;
		}
		else  // arr[mid] > target
		{
			right = mid - 1;
		}
	}
	return left;
}

2.4 - 查找最后一个 = target 的元素位置

int lastEq(int arr[], int size, int target)  // Eq:Equal to
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] <= target)
		{
			left = mid + 1;
		}
		else  // arr[mid] > target
		{
			right = mid - 1;
		}
	}
	if (right >= 0 && arr[right] == target)
		return right;
	return -1;
}

2.5 - 查找最后一个 <= target 的元素位置

int lastLE(int arr[], int size, int target)  // LE:Less than or Equal to
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] <= target)
		{
			left = mid + 1;
		}
		else  // arr[right] > target
		{
			right = mid - 1;
		}
	}
	return right;
}

2.6 - 查找最后一个 < target 的元素位置

int lastLt(int arr[], int size, int target)  // Lt:Less than
{
	int left = 0;
	int right = size - 1;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] < target)
		{
			left = mid + 1;
		}
		else  // arr[mid] >= target
		{
			right = mid - 1;
		}
	}
	return right;
}

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

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1] 

示例 3

输入:nums = [], target = 0
输出:[-1,-1] 

提示

  • 0 <= nums.length <= 10^5

  • -10^9 <= nums[i] <= 10^9

  • nums 是一个非递减数组

  • -10^9 <= target <= 10^9

代码实现

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

int* searchRange(int* nums, int numsSize, int target, int* returnSize) 
{
    *returnSize = 2;
    int* ans = (int*)malloc(sizeof(int) * 2);
    int start = firstGE(nums, numsSize, target);
    // start 为数组中第一个大于或等于 target 的元素位置
    if (start == numsSize || nums[start] != target)
    {
        ans[0] = -1;
        ans[1] = -1;
        return ans;
    }
    // 因为 nums 是整型数组,所以查找最后一个小于或等于 target 的元素位置,
    // 可以转换为查找第一个大于或等于 target + 1 的元素位置,然后将得到的结果减去 1。
    // start 存在,则 end 必定存在,且 nums[end] 就等于 target。
    int end = firstGE(nums, numsSize, target + 1) - 1;
    ans[0] = start;
    ans[1] = end;
    return ans;
}

 

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

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

相关文章

JS类型转换机制

概述 JS中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol&#xff0c;以及引用类型&#xff1a;object 但是我们在声明的时候只有一种数据类型&#xff0c;只有到运行期间才会确定当前类型let x y ? 1 : a; &#xff0c;x的值在编译阶段…

FPGA基础之内置逻辑门

verilog语言中&#xff0c;针对逻辑门&#xff0c;有许多内置可直接使用的逻辑门&#xff0c;从输入输出数量可分为多输入门和多输出门。 一、多输入门 有单个或多个输入&#xff0c;只有单个输出的逻辑门&#xff0c;包含and(与)&#xff0c;or(或)&#xff0c;xor(异或)&am…

在训练心脏数据集时碰到的问题汇总

在训练心脏数据集时碰到的问题汇总&#xff1a; 1.nii数据处理问题 心脏CT数据集采用的是医学图像常用的压缩文件格式nii&#xff0c;且储存的图像为3D图像&#xff0c;不能直接使用。 首先应导入SimpleITK包&#xff0c;利用如下三个函数进行nii格式文件的提取。 sitk.ReadI…

vlan间的通信

vlan之间要通过三层通信实现互访&#xff0c;三层通信需借助三层设备 如果之前配置了 hybrid模式想删除 命令 undo port link-type hybrid vlan all [Huawei-GigabitEthernet0/0/3]dis this interface GigabitEthernet0/0/3 undo port hybrid vlan 1 这里可以理解为多删了一个…

【python】【数据分析】2022年全国大学生数据分析大赛题解-医药电商销售数据分析

文章目录一、前言二、题目三、题解1&#xff0e;对店铺进行分析&#xff0c;一共包含多少家店铺&#xff0c;各店铺的销售额占比如何&#xff1f;给出销售额占比最高的店铺&#xff0c;并分析该店铺的销售情况。2.对所有药品进行分析&#xff0c;一共包含多少个药品&#xff0c…

Promise和async/await

1、回调地狱 多层回调函数的相互嵌套&#xff0c;就形成了回调地狱。示例代码如下&#xff1a; 回调地狱的缺点&#xff1a; 代码耦合性太强&#xff0c;牵一发而动全身&#xff0c;难以维护大量冗余的代码相互嵌套&#xff0c;代码的可读性变差 1.1、如何解决回调地狱的问题…

手把手实现邮件分类 《Getting Started with NLP》chap2:Your first NLP example

《Getting Started with NLP》chap2&#xff1a;Your first NLP example 感觉这本书很适合我这种菜菜,另外下面的笔记还有学习英语的目的&#xff0c;故大多数用英文摘录或总结 文章目录《Getting Started with NLP》chap2&#xff1a;Your first NLP example2.1 Introducing N…

数据结构与算法【树】

二叉树性质 满二叉树 深度为k&#xff0c;有2k−12^{k}-12k−1个结点的二叉树&#xff0c;为满二叉树。 完全二叉树 完全二叉树的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面…

CSDN第22期周赛(记录一下,不是题解)

希望23年能收获一两本程序员杂志 前言 发现一个问题&#xff0c;codeblocks上编译没问题&#xff0c;在CSDN比赛时&#xff0c;会报错&#xff1a; 1&#xff0c;size()和length()属于unsigned int&#xff0c;所以与之比较大小或者赋值的 i, j 也要用unsigned int&#xf…

巧解 JavaScript 中的嵌套替换

网友 wys 提问&#xff1a;如何仅使用 JavaScript 支持的正则语法&#xff0c;将 <p> <table> <p> <p> </table> <table> <p> <p> </table> <p>中<table>...</table>之间的<p>都替换为<b…

C库函数:stdio.h

stdio.h C 标准库 – <stdio.h> | 菜鸟教程 (runoob.com) 下面是头文件 stdio.h 中定义的变量类型&#xff1a; 序号变量 & 描述1size_t 这是无符号整数类型&#xff0c;它是 sizeof 关键字的结果。2FILE 这是一个适合存储文件流信息的对象类型。3fpos_t 这是一个适…

组件的生命周期

一、组件的生命周期 1、组件的生命周期&#xff1a;至一个组件从 创建——>运行——>销毁的过程 2、声明周期函数&#xff1a;由Vue提供的内置函数&#xff0c;伴随组件生命周期按次序自动运行——>钩子函数 3、生命周期的阶段划分 &#xff08;1&#xff09;创建…

什么是链接?(动态链接库和静态链接库的对比)

什么是链接&#xff1f; 首先我们需要知道&#xff0c;一个源文件&#xff08;以.c为例&#xff09;是经过什么最后形成的一个可执行的文件&#xff08;windows下为.exe文件&#xff09;。 一个.c的源文件&#xff0c;要经历 1.预处理&#xff1a;头文件的展开替换 2.编译&…

skywalking解析-如何在idea中调试skywalking agent

当我从github上下载下来skywalking agent的代码后&#xff0c;面临的第一个问题就是如何调试。因为skywalking agent的运行模式与普通程序运行方式不一样&#xff0c;它是通过java agent方式运行的。本文接下来介绍如何在本地调试skywalking agent源码。 目录一、下载源码二、运…

leetcode_栈与队列

栈与队列栈与队列理论基础232.用栈实现队列225.用队列实现栈20.有效的括号1047.删除字符串中的所有相邻重复项150.逆波兰表达式求值239.滑动窗口最大值347.前k个高频元素栈与队列总结栈与队列理论基础 栈与队列理论基础 232.用栈实现队列 力扣题目链接 class MyQueue { pub…

Cadence PCB仿真使用Allegro PCB SI通过导入工艺文件配置层叠结构的方法图文教程

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 目录 1,概述2,配置方法3,总结1,概述 本文简单介绍使用Allegro PCB SI通过导入工艺文件配置层叠结构的方法。 2,配置方法 第1步:打开待仿真的PCB文件,并确认软件为Allegro PCB SI 如果,打开软件不是Allegro PCB SI则…

【JavaScript】数组常用方法

冲突数组常用方法&#xff1a; 注&#xff1a; 以下方法都会对原数组进行改变&#xff1a; push&#xff1a;向数组后面追加元素&#xff0c;返回值是追加后的数组长度 pop&#xff1a;从数组后面删除元素&#xff0c;返回值是删除的元素内容 unshift:在数组前面添加元素&am…

CMMI之系统设计

系统设计&#xff08;System Design, SD&#xff09;是指设计软件系统的体系结构、用户界面、数据库、模块等&#xff0c;从而在需求与代码之间建立桥梁&#xff0c;指导开发人员去实现能满足用户需求的软件产品。系统设计过程域是SPP模型的重要组成部分。本规范阐述了系统设计…

第一章 Flink简介

Flink 系列教程传送门 第一章 Flink 简介 第二章 Flink 环境部署 第三章 Flink DataStream API 第四章 Flink 窗口和水位线 第五章 Flink Table API&SQL 第六章 新闻热搜实时分析系统 前言 流计算产品实时性有两个非常重要的实时性设计因素&#xff0c;一个是待计算…

文档智能(一):基于OpenCV的文档图像校正

文档智能(一)&#xff1a;基于OpenCV的文档图像校正 发表时间&#xff1a; 2023年1月7日创作地点&#xff1a;湖北省武汉市作者&#xff1a;ixy_com&[Aneerban Chakraborty]封面图片来源&#xff1a;DocTr 本文关键词&#xff1a;文档智能、文档图像校正、OpenCV、形态…