解锁“二分魔法”:让算法题轻松找到答案的秘密(1)

解锁“二分魔法”:让算法题轻松找到答案的秘密(1)

前言:

小编在前几日书写了有关于滑动窗口算法题的解析,我承认我最近的学习态度是有问题了,感觉当前的我比前期的我贪玩多了,现在我幡然醒悟,继续开始算法博客的书写,当然,主线的博客我也会一直跟进的,现在小编废话不多说,开始进入今日的算法题讲解~

1.二分算法简介

1.1.二分算法的介绍

二分查找算法(Binary Search)是一种高效的查找算法,核心思想是“分而治之”,适用于在一个有序的数组中快速定位目标值。通过反复将查找范围对半缩小,二分算法显著降低了查找的复杂度,从线性 (O(n)) 降到对数级别 (O(\log n))。它的关键在于精确判断中间元素与目标值的关系,并灵活调整上下界,从而迅速锁定答案。尽管实现相对简单,但在细节处理上却需要严格考虑边界条件,稍有不慎可能出现死循环或逻辑错误。这种算法不仅是面试中的常客,也是高效数据处理的基础工具之一。

1.2.二分算法常出现的误区

二分查找算法虽然简单高效,但在实现过程中存在一些常见误区,可能导致逻辑错误或性能问题:

1. 边界处理不当
  • 误区:容易在循环条件或中点计算时出错,例如在 low <= highlow < high 之间混淆,或者对中点更新后的边界范围处理不当。(这个误区详见小编之后讲的第二个模板的时候会体现出来)
  • 修正:明确上下界的含义,确保在更新 lowhigh 时与循环条件一致。
2. 中点计算溢出
  • 误区:直接计算中点 mid = (low + high) / 2 可能导致整数溢出,特别是在边界值较大的情况下。(这个用法我不推荐)
  • 修正:使用 mid = low + (high - low) / 2 来避免溢出。(以后二分就用它)
3. 忽略重复值或特殊情况
  • 误区:对数组中存在重复值的情况处理不当,可能导致无法正确返回目标的最左或最右位置。
  • 修正:根据题目要求(如找最左边或最右边的目标值),在判断和更新范围时添加必要的逻辑。
4. 提前终止条件设置错误
  • 误区:认为目标值一定存在于数组中,未处理目标值不存在的情况,可能导致返回错误的索引或死循环。
  • 修正:在循环结束后,检查最终位置是否真的包含目标值。
5. 未考虑浮点数或非整数情况【这里写的稍微有点差错】
  • 误区:将二分查找直接应用于浮点数或自定义对象时,忽略比较方法的精度或特性。
  • 修正:对浮点数添加容差范围 (epsilon),对自定义对象实现合理的比较逻辑。
6. 二分查找适用范围理解错误
  • 误区:以为二分查找适用于所有场景,忽略了其前提条件——数组必须是有序的(其实这个是不一定的,数组无序的情况如果知道一些规律也是可以用二分的,这里我写博客写的有点问题,各位读者不要在这里犯错)。
  • 修正:在使用前确保数据有序,必要时进行预处理。
7. 过早优化
  • 误区:过于复杂化代码,为追求极致优化而牺牲可读性和鲁棒性。
  • 修正:优先实现简单清晰的逻辑,确保功能正确后再进行优化。

二分查找看似简单,却隐藏了许多大坑,尤其是细节处理和边界条件,稍有不慎,我们就会跳入二分算法的大坑中,如果能克服这些误区,不仅能掌握算法本身,还能提高解决问题的严谨性和代码质量。

1.3.二分算法的误解

本来我是不想写这个小标题的,但是我突然发觉可能大多数读者对于二分算法的认知就是:把一个数组分成一半分别求解,其实这个说法是不规范的,在之后小编分享的算法题中,有部分题目不能直接咬死一半分,对于二分算法的正确认知,小编认为我们可以通过“二分性”进行二分算法的求解,可能很多读者朋友对于这个还是很陌生的,不要着急,待小编分享了许多题目后,相信做完这些题目的你便会知道这里的”二分性“到底是啥意思,下面废话不多说,待我讲述完二分算法的好处以后,就给各位讲述二分算法的第一个题目。

1.4.二分算法的好处

二分算法有一个和前面的算法题不一样的东西,就是它有模版,很多读者朋友看到这里,想必嘴角已经上扬了,因为模版可以帮助我们快速解决一个算法题,并且不用耗费脑子,仅需读懂题目,然后套用这个模版即可,当然我们肯定可以是这么做的,但是小编还是不推荐各位过度依赖模版的,因为有时候的二分题,光套用模版是做不出来的,但是我还是会讲述一下它们的,便于一些读者更方便的使用二分查找算法,模版一共有三种,今天小编先讲述其中一种——朴素的二分模版,剩下的小编会在下一篇文章讲解,下面废话不多说,正式开始我们今天的算法题目讲解。

2.二分查找

2.1.题目来源

老规矩,小编先给出本题目的来源:704. 二分查找 - 力扣(LeetCode)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2.题目解析

本题目的名字就和文章标题一样,所以其实这个题目就告诉了我们应该用什么方法来做这个题,那就是二分算法,当然具体的思路讲解会在后面写到的,这里我先简单的说一下本题目的解法,这个题目其实就是想让我们在一个有序的数组中查找一个目标值,如果找到了这个目标值,就返回它的下标,如果没有找到就返回-1就好,题目理解起来很简单,下面小编直接进行思路讲解。

2.3.题目思路讲解

1.暴力解法

如果这个题目不是这个标题的话,小编相信大多数读者朋友都会想要一个非常暴力的解法——把数组遍历一遍来寻找我们想要寻找的target,小编认为这个解法是很容易想出来的,当然,这么做的这个题目的时间复杂度也是有点高的(和二分的比较),那就是O(N),N表示的数组的长度,如果数组中没有该元素的话那么我们就要遍历整个数组了,所以出现这样的情况也是很麻烦的,所以小编认为暴力解法我们想象就好了,实践的话还是不要用二分算法比较好,下面小编给出本题目正确的解法。

2.二分查找

本题目用的解题方法自然就是和题目名字一样的解法,那就是二分查找算法,想要使用二分查找算法,我们首先需要找到本题目的“二分性”,此时我们通过题目,可以知晓这是一个升序的数组,所以我们可以先找到升序数组中中间位置的值,之后让中间位置的值和给定的值进行比较,如果target比中间位置的值大,那么就从右边区间找;反之则就往左边找,通过一次次的二分区间,我们就可以找到想要的值,此时小编解答一下为什么我们要寻找中间位置的值——因为此时的数组是升序的,中间位置的值肯定是比左区间的值大的,此时它就可以作为一个标志,用来判定此时的target属于哪个区间的标志,当然我们也可以三分查找,四分查找等等,但是经过数据分析,二分查找是在这些查找中比较优秀的解法,所以我们还是中间分比较好,本题的解法就是这样,通过一次次二分区间来找到我们想要的数,因为是二分算法的第一次讲述,下面小编就用一个例子来帮助各位更好的理解二分算法。

为了本题目的广义性,小编就不用一个特殊的例子来给各位讲述二分了,取而代之的是用一个一定长度的数组来代替详细的例子,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时我们需要借助两个“指针”,来代表区间的首尾,一个指针指向数组的开头,用left表示;另一个指针指向数组的末尾,用right表示;之后我们需要先找到中间结点的位置,具体运算公式小编在上面体现出来了,这里我就不多说了,然后我们可以划分出两个区间:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时我们比较target和mid位置数据的大小,如果目标值比mid大的话,那么我们就要往右边区间进行查找,此时我们让mid + 1直接称为左区间就可以,因为此时mid + 1之前的数肯定是比target小;如果此时target比mid位置的数据小,那么我们仅需在左边区间进行查找即可,此时我们让mid - 1称为右区间即可,之后经过我们不断的划分区间,直到left大于right或者找到目标值的时候,此时我们返回中间位置的下标或者返回-1表示没找到,上述就是本题目的思路讲解,下面小编进行代码实操。

2.4.代码实操

首先,我们需要先把二分查找的工具准备好,分别是左右指针,中间位置mid,代码如下所示:

int left = 0,right = nums.size() - 1,min = 0;

之后,我们就要开始进行二分查找了,因为我们需要不断的划分区间,所以此时我们需要用到循环来帮助我们解决问题,循环的条件自然是左右指针不能重合,下面给出这部分的代码:

 while(left <= right)
 {}

进入循环体,此时我们需要先找到中间位置,此时我们借助上面小编讲述的查找中间位置的方法即可,这么做可以避免数据溢出的情况,之后我们判断此时的mid位置的值和target的大小,如果target大于mid的话,那么我们就从mid右边区间进行查找,此时我们直接让left = mid + 1即可;如果此时的target小于mid的话,那么我们要在mid左区间进行查找,此时我们直接让right = mid - 1即可;如果相等的话,我们直接返回mid接即可;下面是本部分代码展示:

min = left + (right - left) / 2;  
if(nums[min] > target)
{
    right = min - 1;
}
else if(nums[min] < target)
{
    left = min + 1;
}
else
{
    return min;
}

最后,如果循环结束我们还是没有返回值,那么我们直接返回-1,代表数组里面没有我们想要的值,如下所示:

return -1;

2.4.朴素的二分模版

此时我们已经解决完了上面那道题,上面的那道题是在二分查找题目中小编认为是最简单的题目了,不过这个题的价值,是告诉了我们二分查找算法的第一个模版——朴素的二分模版,如果我们遇到一个二分查找算法,发现这个题目和此题目很相似的时候,我们就可以套用这个模版,下面小编给出这个模版,但是小编还是推荐各位遇到二分查找题目的时候,通过自己找到“二分性”来解决题目,而不是死记模版,有时候模版记错了,那么就可能gg了,用脑子做题,往往比死记硬背更好。

while(left <= right)
        {
            min = left + (right - left) / 2;  
            if(......)//根据题目填
            {
                right = min - 1;
            }
            else if(......)//根据题目填
            {
                left = min + 1;
            }
            else
            {
                return min;
            }
        }

2.5.完整题目代码展示

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0,right = nums.size() - 1,min = 0;
        while(left <= right)
        {
            min = left + (right - left) / 2;  //这样大概率是会溢出的
            if(nums[min] > target)
            {
                right = min - 1;
            }
            else if(nums[min] < target)
            {
                left = min + 1;
            }
            else
            {
                return min;
            }
        }
        return -1;
    }
};

3.总结

本篇文章到这里也就结束了,二分查找其实我们如果真的领悟了它的本质以后,这就是我们在所有的算法中最简单的算法(小编自我认为的,一千个读者就有一千个哈姆雷特),各位一定要好好领悟上面题目的解法,小编相信,各位读者朋友在看完我二分查找精选算法题的讲解的时候,以后当我们遇到二分查找算法的时候,直接可以“秒杀”(突然感觉我有点自信了),一起写算法题的时候总是短暂的,那么各位大佬们,我们下一篇文章见啦!
在这里插入图片描述

评论 99
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值