《二分查找:从 “折半” 到 “精准命中” 的算法逻辑拆解》

《二分查找:从 “折半” 到 “精准命中” 的算法逻辑拆解》
前引:算法面试中,二分查找是 “高频考点” 之一,它不仅能考察求职者的逻辑思维,还能检验对时间复杂度优化的理解。而在实际开发中,二分查找更是处理 “有序数据查找” 问题的最优解无论是缓存查找、数据索引,还是参数优化,都能看到它的身影。但很多开发者对二分查找的理解停留在 “基础用法”,忽略了其在复杂场景下的拓展应用,也未能规避常见的边界错误。本文将结合面试真题和实战案例,全面解析二分查找的原理、优化技巧、场景延伸,帮你既能轻松应对面试,又能在实际开发中高效运用,真正发挥二分查找的 “效率优势”!

目录

【一】“二分”算法原理剖析

【二】简单的二分查找

(1)题目链接

(2)算法解析

【三】找目标范围

(1)题目链接

(2)算法解析

(3)代码

【四】搜索插入位置

(1)题目链接

(2)算法解析

(3)代码

【五】寻找旋转数组中的最小值

(1)题目链接

(2)算法解析

(3)代码


【一】“二分”算法原理剖析

“二分”的刻板印象就是需要目标有序,即0,1,2,3,4,5.....但是“二分”的本质:通过目标值排除达到一半的区间,解决传统的从头到尾的遍历查找,只要目标数据与目标值满足一定的大小关系,下面是三套二分模板,我们开始推:

第一套模版:

 int left=0,right=nums.size()-1; int media=0; //找左端点 while(left<right) { media = (right+left)/2; if(nums[media]>target)right=media-1; else if(nums[media]<target)left=media+1; }

第二套模板:

推论:假设找target连续的区间(找左端点)

首先看左边区间:如果mid落在左边的区间,那么mid不可能命中到8,left=mid+1

                             8在右边的一坨中,那么mid不能超过这个区间(竖划线),right=mid

                             mid的中值计算应该为:left+(right-left)/2(计算左端点)

                             循环条件应该是:left<right,如果等于,会由于判断条件导致循环

现象:如果mid落在左边,就必须超过竖划线收缩;在右边应该不断收缩,但是不能超过竖划线

 int left=0,right=nums.size()-1; int media=0; //找左端点 while(left<right) { media = left+(right-left)/2; if(nums[media]>=target)right=media; else if(nums[media]<target)left=media+1; }

第三套模板:

推论:假设找target连续的区间(找右端点)

首先看左边区间:如果mid落在左边的区间,那么mid可能命中到8,left=mid

                             mid落在右边的一坨,那么mid不能超过这个区间,right=mid-1

                             mid的中值计算应该为:left+(right-left+1)/2(计算右端点)

                             循环条件应该是:left<right,如果等于,会由于判断条件导致循环

现象:如果mid落在左边,就不能超过竖划线收缩;在右边应该不断收缩,必须要超过竖划线

 //找右端点 left=0,right=nums.size()-1; while(left<right) { media = left+(right-left+1)/2; if(nums[media]>target)right=media-1; else if(nums[media]<=target)left=media; }

【二】简单的二分查找

(1)题目链接

https://leetcode.cn/problems/binary-search

(2)算法解析

这题大家套“二分算法原理剖析”的第一套模板即可

【三】找目标范围

(1)题目链接

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array

(2)算法解析

暴力解法:遍历数组,找先目标值,再看是否有连续的目标值,记录它们的下标即可

算法解析:(以下是找的到target的情况,需要判断找不到的情况)

首先找左端点:

以上图为例,具备完美的边界(二段性),左边界的8比左边的0,3,5,6都要大,如果mid落在边界左边,不管怎样都要找比mid位置大的数,所以如果小于目标值,left=mid+1

那么如果mid落在>=8(左边界的8)的位置,right不应该跳过且收缩,所以right=mid

其次是右端点:

以上图为例,具备完美的边界(二段性),同理:

如果落在最右边8的左边,那么left不能跳过这段区间,只能是left=mid,不断收缩

如果落在最右边8的右边,那么right最终肯定要跳过才行,所以right=mid-1,跳过+收缩

(3)代码
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if(nums.size()==0)return {-1,-1}; vector<int> V; int left=0,right=nums.size()-1; int media=0; //找左端点 while(left<right) { media = left+(right-left)/2; if(nums[media]>=target)right=media; else if(nums[media]<target)left=media+1; } if(nums[left]==target) { V.push_back(left); } else//如果找不到 { V.push_back(-1); } //找右端点 left=0,right=nums.size()-1; while(left<right) { media = left+(right-left+1)/2; if(nums[media]>target)right=media-1; else if(nums[media]<=target)left=media; } if(nums[left]==target) { V.push_back(left); } else//如果找不到 { V.push_back(-1); } return V; } };

【四】搜索插入位置

(1)题目链接

https://leetcode.cn/problems/search-insert-position

(2)算法解析

对于有二段线的数组+目标值的,我们采用二分的方法,下面是思路,采用第几套模板:

我们观察这两组值:

nums = [1,3,5,6], target = 2

nums = [1,3,5,6], target = 7

可以看到:找的都是稍大的位置,比如第一组目标位置是1号下标,那么如果nums[i]<2,有没有可能?完全没有,所以如果算出的目标值比小,那么left=mid+1,只要确定了一边,那么另一边就出来了,right=mid,循环条件是left<right,最后肯定是left和right相遇出循环,此时判断是不是目标值,再做返回值处理

(3)代码
class Solution { public: int searchInsert(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; int media = 0; //找左端点 while (left < right) { media = left + (right - left) / 2; if (nums[media] >= target)right = media; else if (nums[media] < target)left = media + 1; } if(nums[left]<target)return left+1; return left; } };

【五】寻找旋转数组中的最小值

(1)题目链接

https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array

(2)算法解析

对于数组中找值的这类题目,我们先看有没有二段性,很明显有:数组中最小的元素

每旋转一次是把最后一个值拿到前面来,因此,就像一个蜿蜒的山峰:

因此可以以nums[0]和nums[size-1]来作为基准值:以nums[0]为例:

如果比nums[0]大,说明在数组第二象限,left=mid+1(因为不可能在这段区间),反之如果<它,那么可能在第四象限,就需要缩小区间,right=mid,切记不能跳过mid,最后处理边界情况:

如果left一直满足left=mid+1,出循环也就是left==nums.size( ),比如0,1,2,3,4,即left=right的时候

(3)代码
class Solution { public: int findMin(vector<int>& nums) { int left=0,right=nums.size(); while(left<right) { int mid = left+(right-left)/2; if(nums[mid]>=nums[0])left=mid+1; else right=mid; } return left == nums.size() ? nums[0] : nums[left]; } };

Read more

MySQL 从入门到精通完全教程

目录 1. 前言 2. MySQL 基础认知 3. MySQL 安装与配置 4. MySQL 核心语法 5. 高级查询技巧 6. MySQL 函数 7. 数据约束 8. 事务管理 9. 索引优化 10. 存储过程与函数 11. 用户与权限管理 12. 性能优化实战 13. 常见问题与解决方案 1. 前言 1.1 什么是MySQL? MySQL 是一款开源的关系型数据库管理系统(RDBMS),基于SQL(结构化查询语言)实现数据管理,广泛应用于Web开发(如PHP+MySQL、Python+MySQL),特点是轻量、高效、跨平台、

By Ne0inhk
MySQL 进阶:库与表的DDL核心操作全指南(含实战案例)

MySQL 进阶:库与表的DDL核心操作全指南(含实战案例)

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 数据库(库)的核心操作 * 1.1 创建数据库:指定字符集与校验规则 * 1.1.1 语法格式 * 1.1.2 实战案例 * 1.2 字符集与校验规则:影响查询和排序 * 1.2.1 查看系统默认配置 * 1.2.2 查看支持的字符集和校验规则 * 1.2.3 校验规则的实际影响 * 1.3 操纵数据库:查询、修改、

By Ne0inhk
SpringBoot 整合 Langchain4j 实现会话记忆存储深度解析

SpringBoot 整合 Langchain4j 实现会话记忆存储深度解析

目录 一、前言 二、AI大模型会话记忆介绍 2.1 AI 大模型的会话记忆是什么 2.2 AI 大模型为什么需要会话记忆 2.3 AI 大模型会话记忆常用实现方案 2.4 LangChain4j 会话记忆介绍 2.4.1 LangChain4j 会话记忆介绍 2.4.2 LangChain4j 会话记忆类型 三、Langchain4j 会话记忆操作案例使用 3.1 前置准备 3.1.1 导入依赖文件 3.1.2 添加配置文件 3.1.3 前置案例 3.

By Ne0inhk