leetcode hot100_part30_二分查找

news2024/9/21 18:59:51

        上次写博客已经一个月了,这段时间在做外卖项目,真的啥也没有就是个小玩具。

        上次接触是在最长递增子序列的一个题解里,复习一下已经忘完了。如果我们在一个有序数组中进行查找某个target,一般肯定就是从小到大or从大到小遍历查找,那么如果我们知道了有序数组的中间位置的数据的具体值,我们就可以只搜索数组的某一半来找target,这就是二分查找。

等号细节

首先 left 和 right 分别指向首尾,细节的点是

  1. while( left ?? right ) 里的条件是小于还是小于等于
  2. if ( nums [mid]  < target) left = mid + 1 还是 mid

结合代码随想录的视频,首先弄清想写的是开区间还是闭区间,怎么写取决于你的定义,

  1. 如果是 [ left,right ] 也就是说搜索区间为左闭右闭,那么当 left == right 的时候是合法区间,可以相等,比如[1,1],所以 <=。
  2. 继续,那么当 nums [mid] <  target 的时候,我们可以肯定确定,mid不是我们要找的数,不在搜索区间内了,因为我们定义的是左闭右闭区间,所以搜索的区间更新为了[ mid +1, right ],即 left = mid + 1,如果 left 还等于 mid,在 left 和 right 相邻的时候会死循环。

容易理解并写出左闭右开区间的代码,但左闭右开的时候 right 不是length - 1而是 length。其他区间的开闭写法以此类推。

//左闭右闭
while(left <= right){
    mid = left + ((right-left) >> 1);
    if(nums[mid] > target)
        right = mid - 1;
    else if(nums[mid] < target)
        left = mid + 1;
    else
        return mid;
}
return -1;



//左闭右开
right = nums.length;
while(left < right){
    mid = left + ((right-left) >> 1);
    if(nums[mid] > target)
        right = mid;
    else if(nums[mid] < target)
        left = mid + 1;
    else
        return mid;
}
return -1;

找上下界

参考:📝【LeetCode】一个模板通杀所有「二分查找」问题 (imageslr.com)

什么是上下界已经不重要了,这里简单说,就是如果有很多target,怎么找到第一个和最后一个target 值的index 以及 target 区间的前一个和后一个的下标。

1.第一个 >= target  的位置:index_1

        这里包含两种情况,首先是很多 target 的第一个位置,还有就是没有target,第一个比target大的位置,怎么写代码还是取决于你怎么定义,但是做出这个定义是有点难度的。

左闭右闭

        我们采用左右闭区间的写法,怎么定义呢,我们定义:

  • 所有小于 target 的元素都在 left 的左侧,不包含 left,即从[ 0,left -1 ] 的元素值都是小于target的。对应的:if ( nums[ mid ] < target ) left = mid + 1
  • 否则,也就是所有大于等于 target 的元素都在 right 的右侧,不包含right,即从[ right + 1,length -1 ]的值都是大于等于target的。对应的:if(nums[ mid ] > = targrt)right = mid -1;
  • 我们采用的是闭区间的写法,也就是说在while循环里最后区间肯定是不为空的,边界就是left和right指向同一个位置,跳出循环时肯定是 left在right的下一个位置,因为不论最后一步left还是right移动都是这个结果。如下图,target = 5;
  • 那么就很显然了,最后的状态left在right的下一个位置,而right后面的根据我们的定义就是比targrt大的元素,而且数组是有序的,left就是我们要找的 index_1。

//left和right的初始化也要注意
while(left <= right){
    mid = left + ((right-left) >> 1);
    if(nums[mid] >= target)
        right = mid - 1;
    else left = mid + 1;
}
return left;

        index_1其实也是顺序插入target的 位置。如果要找的是第一个 > target 的元素,只要把上面代码里的 >= 改成 > 就行,其实最终的结果就是 left 侵入到了 right 右边的区间,相互侵入

左闭右开

        左闭右开的情况怎么写呢?首先right是length,然后while循环条件是小于,说明跳出循序的状态是 left == right,因为是[ left,right ),所以right更新的时候是 right = mid(此时right相当于上图黄色板块的第一个元素)。最后left和right指向一个位置,最后的搜索区间只有left,而不像左闭右闭那样为空。根据定义,[ 0,left -1 ]都是小于target 的元素,left是最后的区间结果,left == right 重合,即[ left,right ),[ right,length-1 ]是大于等于 target 的元素,所以 left 就是我们要的结果。

right = nums.length;     //变化1
while(left < right){     //变化2
    mid = left + ((right-left) >> 1);
    if(nums[mid] >= target)
        right = mid;     //变化3
    else left = mid + 1;
}
return left;              //变化4,返回right也可以

2.最后一个 <= target 的位置:index_2

        如果有连续相等的target,那么结果就是最后一个 target 的位置,如果没有target,那么就是index_1的前一个位置。

        按照上面的思考,是需要把上面的模版 > = 换成大于,然后返回 right 或者 left -1。关键的关键还是定义问题。left的左边全是 <= target 的元素。

3.target出现的第一个位置

        其实就是index_1那种情况加个判断,先判断是否越界,再判断nums[ index_1 ] 是否为target

while(){
.....
}
if( left >= nums.length || nums[left] != target){
    return -1;
}
return left;

4.target出现的最后一个位置

        index_2的变形,right是否越界,nums[ right ]是否为target

while(){
    //index_2代码
}
if(right < 0 || num[right] != target){
    return -1;
}
return right;

总结

关键在于分析出:

  1. 跳出while循环时左右指针的状态,相邻(left在right后面)还是重合
  2. 左右指针两侧值的含义,这个含义包不包括左右指针
  3. 左右相互入侵


题目

35.搜索插入位置

直接套index_1,这里发现了一个小问题,就是计算mid的时候,(right - left) >> 1这个外面要加括号,不能写成 mid = left + (right - left) >> 1,这样就超时了。因为+-运算符的优先级要比位运算高,上面模版一开始写错了,后面改的。

74.搜索二维矩阵

        非严格递增就是存在相等的情况,严格递增就是没有等于的情况。

        在矩阵中进行搜索,总体的思路是先找到 target 所在的行,然后再在那一行中寻找。

        因为根据第二个条件,行到下一行的衔接处是严格严格递增的,能保证:我们在第一列中进行搜索,找到第一个大于 target 的行 x,它的上一行就是target所在的行。反证一下,如果target所在的行是 x - 2 行,那根据第二个条件,x - 1行的第一个数肯定比 target 大,那么找到的结果就是 x- 1行而不是x。

        这里注意如果x是0,第0行,那target肯定是不存在的。

        然后再目标行中进行寻找,找到第一个大于等于target的index,可行。但是为了代码复用,还是找的第一个大于target的index,同理如果index为0,target不存在。

        最后对目标行的 index - 1 处进行判断是否等于target就行。

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

        懒得看题解了,不就是上面总结的情况3和情况4?

以上内容6/19

中间花了有10天做了BI项目

today7/16

33.搜索旋转排序数组

        题目要求是 logN 的算法,也就是说,我们要用二分查找,如果先遍历一遍找到旋转位置,那么复杂度就是N了。

        之前看了题解,了解了大致的思路,还是要找到特点,数组的特点。左半数组的元素都比右半数组的大。

        总的来说,就是细化搜索细节,分类讨论,官方的是先讨论mid的位置,自己写的是先考虑target的位置。注意等号,边界条件的考虑,还有如果旋转后的数组还是递增的,还成立吗?

        二分缩小查找区间,一开始的查找区间可能不是有序的,但是我们先做的是缩小,随着区间的不断缩小,最后的区间一定是有序的。无序区间到有序的转换。

        确定mid或者target在左区间还是右区间的时候,最好还是和区间的左右边界比较,left or right,和第一个or最后一个数比较可能会出错。后面二刷的时候再改吧。

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

        脑子真是抽了哈,想到的思路是对的,然后又去用33题官方题解的方法找最后一个元素,真是傻逼了,不该熬夜的。

        始终让查找区间保存在这种状态,区间要跨越(也就是包括)最小值,也就是包括数组单调性的转折点,当区间长度为1的时候就是结果了。

        二分查找,灵活运用起来是一种筛选,按需要条件缩小区间左右边界的操作。

        考虑旋转数组是递增的情况。

下面这个题解四道都说了:

153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

4.寻找两个正序数组的中位数

先按照这个题解的思路分析:

4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

        其中第三种方法关于怎么找到第K小的数的思路很好啊!

        回到第四种方法上,没看官方题解,就是,隐式的找中位数,不用在合并去找中位数,而是通过个数,两个数组被中位数分割的前半部分元素个数为长度除2,

  1. 前半部分元素个数和为定值。
  2. 然后还要满足前半部分所有元素都小于后半部分所有元素;
  3. 通过二分法找切割位置。切割位置也对应着个数关系,且和为定值。

结合上面三个要点,利用二分法找到切割位置。

遇到的问题:

  1. i,j到底是切割位置前还是后,要好好推导一下
  2. 循环条件里的 =,不带等于号应该也可以,但是比较麻烦,没细想了
  3. 越界判断要很严谨

先这样吧,后面再补充

        

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

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

相关文章

js reduce 的别样用法

let mergedItems list.reduce((accumulator, currentItem) > {let existingItem accumulator.find((item) > item.manObject_name currentItem.manObject_name);if (existingItem) {existingItem.laborCostHand currentItem.laborCostHand; //劳务费existingItem.wor…

【网络】Socket编程

文章目录 正确理解端口号理解源IP地址和目的IP地址认识端口号端口号和进程ID 理解Socket网络字节序socket编程接口创建socket套接字bind绑定套接字listen建立监听accept接受连接connect建立连接sendto发送数据接收数据close关闭套接字 sockaddr结构体 正确理解端口号 理解源IP…

Windows 电脑部署 ollama3 并安装模型

Windows 电脑部署 ollama3 并安装模型 部署中为了尽可能减少对本地环境的污染&#xff0c;使用 Docker 安装&#xff01; github: https://github.com/ollama/ollama 准备部署文件 version: 3.8services:ollama:volumes:- ./models:/root/.ollama # 将本地文件夹挂载到容器中…

独立游戏《星尘异变》UE5 C++程序开发日志7——实现加载界面

目录 一、创建自定义AssetManager类 二、异步加载资产 三、加载界面UI的实现 1.UI布局 2.在打开关卡前和进入关卡后创建UI并统计进度 有时可能会遇到关卡已经进去但依然会卡顿一段时间的情况&#xff0c;所以我们需要在进入关卡后&#xff0c;玩家可以操作关卡之前&#x…

RAG技术进化史:从初级到高级,再到模块化RAG架构的创新发展

大型语言模型&#xff08;LLMs&#xff09;通过在自然语言任务及其它领域的成功应用&#xff0c;如 ChatGPT、Bard、Claude 等所示&#xff0c;已经彻底改变了 AI 领域。这些 LLMs 能够生成从创意写作到复杂代码的文本。然而&#xff0c;LLMs 面临着幻觉、过时知识和不透明、无…

Codeforces Round 672 (Div. 2) C1. Pokémon Army (easy version) (DP)

不知道能不能用贪心&#xff0c;反正我是没看出来&#xff0c;所以用DP求解。 首先分析一下题意&#xff0c;我们要在一段序列中取出一段子序列&#xff0c;然后让这段子序列按顺序逐个先加后减最终得到的结果最大。 如果要用DP&#xff0c;那么我们首先就要思考怎么表示状态…

心好累,早点睡!永远不要提前焦虑——早读(逆天打工人爬取热门微信文章解读)

你们遇到心烦的事&#xff0c;会怎么排解呢&#xff1f; 引言Python 代码第一篇 洞见 永远不要提前焦虑第二篇 故事来源于生活结尾 引言 这两天天气都是阴雨 雨时而大 时而小 就是下个不停 老天还算给面子 上班路上总是细雨或者无雨 昨天晚上回来 山地车的前轮有些送动 马上发…

C++从入门到起飞之——this指针 全方位剖析!

个人主页&#xff1a;秋风起&#xff0c;再归来~ C从入门到起飞 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1、this指针 2、C和C语⾔实现Stack对⽐ C实现Stack代码 C实现Stack代…

深度挖掘行情接口:股票市场中的关键金融数据API接口解析

在股票市场里&#xff0c;存在若干常见的股票行情数据接口&#xff0c;每一种接口皆具备独特的功能与用途。以下为一些常见的金融数据 API 接口&#xff0c;其涵盖了广泛的金融数据内容&#xff0c;其中就包含股票行情数据&#xff1a; 实时行情接口 实时行情接口&#xff1a…

光耦合器技术的实际应用

光耦合器也称为光隔离器&#xff0c;是现代电子产品中的关键组件&#xff0c;可确保电路不同部分之间的信号完整性和隔离。它们使用光来传输电信号&#xff0c;提供电气隔离和抗噪性。 结构和功能 光耦合器通常由以下部分组成&#xff1a; 1.LED&#xff08;发光二极管&#…

Bone Collector-动态规划题解

Bone Collector Problem - 2602 (hdu.edu.cn)https://acm.hdu.edu.cn/showproblem.php?pid2602 Problem Description Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’…

Ubuntu18 中JDK的安装

文章目录 一、背景说明二、获取安装包三、安装JDK3.1 上传安装包3.2 复制和解压3.3 环境变量的设置3.4 验证安装 四、问题列表4.1 .bashrc文件在哪里&#xff1f;.bashrc是什么&#xff1f;4.2 为什么使用rz上传安装包时会报&#xff1a; 传输失败&#xff1f; 五、总结 一、背…

SpringBoot项目中将word转换为pdf

需求&#xff0c;用户将用户上传的word文件转换成为pdf格式&#xff0c;然后返回 第一步&#xff1a;引入依赖 <dependency><groupId>aspose</groupId><artifactId>aspose-words</artifactId><version>15.8.0-jdk16</version></…

走进标杆 | 宁波市领导一行至金鸡强磁开展专项调研活动

为深入了解supOS助力宁波打造数实融合标杆城市的实践成果&#xff0c;日前&#xff0c;宁波市领导和专家共赴蓝卓supOS标杆项目——宁波金鸡强磁股份有限公司&#xff08;以下简称“金鸡强磁”&#xff09;调研考察&#xff0c;蓝卓总经理助理杨明明、浙江大区副总经理章来胜等…

力扣3202:找出有效子序列的最大长度||

class Solution { public:int maximumLength(vector<int>& nums, int k) {int res0;for(int m0;m<k;m){//假设子序列两数%k之后的结果为m 相当于枚举vector<int> v(k,0);for(auto num:nums){v[num%k]v[(m-num%kk)%k]1; //知道m之后可以知道需要的子序列当前…

做外贸如何高效跟进转化客户

做外贸业务&#xff0c;基本上每天要做的几个事情&#xff0c;开发客户、跟进转化客户、维护客户等等&#xff0c;经常也有外贸新手朋友问我&#xff0c;遇到什么什么客户要怎么跟进&#xff0c;客户不回复怎么办等等&#xff0c;今天就来跟大家聊聊客户跟进转化这块的一些问题…

通俗易懂讲解电池的主要性能参数(下)

接上期内容&#xff0c;上次主要分享影响电池性能评估的两个主要性能参数&#xff1a;电池容量和电池功率。可以点击这里回顾&#xff1a;一起学习电池的主要性能参数和测试方法 今日接着分享电池性能评估的另外两个主要性能参数&#xff1a;电池的使用寿命和自放电率。 上期…

react 快速入门思维导图

在掌握了react中一下的几个步骤和语法&#xff0c;基本上就可以熟练的使用react了。 1、组件的使用。react创建组件主要是类组件和函数式组件&#xff0c;类组件有生命周期&#xff0c;而函数式组件没有。 2、jsx语法。react主要使用jsx语法&#xff0c;需要使用babel和webpa…

SSM 整合(Spring + MyBatis;Spring + Spring MVC)

1. SSM 整合(Spring MyBatis&#xff1b;Spring Spring MVC) 文章目录 1. SSM 整合(Spring MyBatis&#xff1b;Spring Spring MVC)2. 引入相关依赖3. SSM 整合3.1 创建包结构 4. Spring 整合 MyBatis4.1 编写 jdbc.properties4.2 编写 DataSourceConfig 数据源配置4.3 编…

样式布局学习:盒模型浮动FlexboxGrid Layout定位(Positioning响应式布局(Responsive Layout):

看视频确实更容易理解&#xff0c;但是花费时间有点长了&#xff0c;以下是一些官方文档网址&#xff08;可切换中文&#xff09;&#xff0c;也算是节省时间了&#xff1a; 在前端开发中&#xff0c;布局样式是非常重要的一部分&#xff0c;它决定了页面中元素的位置和排列方…