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

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

目录

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

【二】简单的二分查找

(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

一个 skill ,增加大模型前端的审美能力

上周,我让 AI 帮我做个落地页。 十分钟过去了,生成出来的东西—— 白色背景,紫色渐变,Inter 字体。 我直接关了。 你也遇到过吧? 用 AI 生前端,出来的东西都长一个样。 背景非白即黑,标题栏永远是紫色渐变,字体不是 Inter 就是 Roboto,配色永远是那套蓝绿红黄。 不是说不能用,但—— 太像 AI 了。 一眼看过去就是"机器生成",没有灵魂,没有个性。 直到昨天,我发现了一个东西。 Anthropic 官方出的一个 skill,叫 frontend-design。 让我再试一次。 这次不一样了 同样的提示词,同样的模型。 我只加了一句话: “使用 frontend-design skill” 结果呢?

By Ne0inhk
Flutter 三方库 arcade 的鸿蒙化适配指南 - 实现高性能的端侧 Web 框架、支持轻量级 HTTP 路由分发与服务端逻辑集成

Flutter 三方库 arcade 的鸿蒙化适配指南 - 实现高性能的端侧 Web 框架、支持轻量级 HTTP 路由分发与服务端逻辑集成

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 arcade 的鸿蒙化适配指南 - 实现高性能的端侧 Web 框架、支持轻量级 HTTP 路由分发与服务端逻辑集成 前言 在进行 Flutter for OpenHarmony 的全栈式开发或特定的边缘计算场景,我们有时需要在鸿蒙应用内部直接启动一个功能完备但又极其轻量的单文件 Web 服务器。arcade 是一个主打微核心设计的 Dart 服务端框架。它能让你在鸿蒙真机上以最少的内存占用,快速运行起一套处理 REST 请求的逻辑中心。本文将指导大家如何在鸿蒙端利用该框架构建微服务。 一、原理解析 / 概念介绍 1.1 基础原理 arcade 采用了非阻塞式的 IO 事件循环架构。它通过直接包装 dart:io 的 HttpServer,提供了一套高度流式(

By Ne0inhk
LeetCode 385 迷你语法分析器

LeetCode 385 迷你语法分析器

文章目录 * 摘要 * 描述 * 题解答案 * 题解代码分析 * 1. 特殊情况处理 * 2. 使用栈来维护嵌套结构 * 3. 数字解析 * 4. 处理逗号和右括号 * 5. 完整解析流程示例 * 6. 边界情况处理 * 示例测试及结果 * 示例 1:单个整数 * 示例 2:嵌套列表 * 示例 3:包含负数 * 示例 4:空列表 * 示例 5:多层嵌套 * 时间复杂度 * 空间复杂度 * 实际应用场景 * 场景一:JSON 解析 * 场景二:配置文件解析 * 场景三:表达式求值 * 场景四:代码解析 * 场景五:数据序列化和反序列化 * 总结 摘要 这道题其实挺有意思的,

By Ne0inhk
归并排序时间复杂度O(nlogn)深度解析:以LeetCode 148.排序链表为例

归并排序时间复杂度O(nlogn)深度解析:以LeetCode 148.排序链表为例

归并排序时间复杂度O(nlogn)深度解析:以LeetCode 148.排序链表为例 LeetCode 148.排序链表 一、引言 在刷LeetCode 148.排序链表时,很多同学会对归并排序的时间复杂度O(nlogn)感到困惑:为什么它一定能达到这个复杂度?分解和合并的过程具体是如何贡献这个复杂度的?本文将通过详细的图解和代码分析,揭开归并排序时间复杂度背后的数学原理。 二、问题背景:LeetCode 148.排序链表 题目要求对链表进行排序,进阶要求时间复杂度O(nlogn)且常数级空间复杂度。归并排序正是满足这一要求的经典解法。 示例: * 输入:4 → 2 → 1 → 3 * 输出:1 → 2 → 3 → 4 三、归并排序的核心思想:分而治之 归并排序采用分治策略,将排序问题分解为三个步骤: 1. 分解:将原问题分解成若干个规模较小的子问题

By Ne0inhk