十分好用的二分查找模板 手撕二分还怕吗?

简介: 十分好用的二分查找模板 手撕二分还怕吗?

最近发现一个二分查找很好用的模板,花了一点时间理解了下这个模板,然后这两天就会一直去找二分查找的题,利用那套模板来实现。这套模板和传统模板的不同在于传统的模板,可能我们会这样写:

function binarySerach($data, $res)
{
        $left  = 0;
        $right = count($data) - 1;
        while ($left <= $right) {
            $middle = $left + (($right - $left) >> 1);
            if ($data[$middle] == $res) return $middle;
            else if ($data[$middle] > $res) $right = $middle - 1;
            else $left = $middle + 1;
        }
        return -1;
 }

这样写的缺点在哪呢,有两个点,一个是 while 里面产生了三个分支,其实大部分情况下,我们应该只需要去区分一下是否需要排除掉中位数,至于结果的处理,应该留到最后具体的场景实现。第二点就是 (这里看不出来,不过我可以举个场景),比如结果不存在,那么返回第一个大于或者第一个小于这类的场景。这时候因为你 while 的条件是小于或者等于,最后如果没有符合条件,需要格外返回别的数据以满足题目需求,这时候你必然要根据当前的语境去判断是返回 left 还是 right, 可能有些时候还会把自己绕晕,如果场景复杂的话。


下面我们来稍微改进一下当前模板来做下一道题目。


1668570727467.jpg


题目描述


题目让我们找出重复的数,给定的数组的值都在 1-n 之间,数组总数是 n+1, 那么必然有数字是重复的,让我们来找出这个重复的数。


题目分析


这道题可以有更好的解法 (我这里强行把它当做二分的场景),二分的解题思路就是取 n 的中位数,遍历数组,统计不大于中位数的个数,如果不大于中位数的个数比中位数还大,说明重复的数在中位数的左边 (注意包括中位数自己,理解一下这一句),否则的话重复的数肯定出现在中位数的右边,而且肯定不包括中位数。好了下面看代码再细讲。


代码实现


/**
     * @param Integer[] $nums
     * @return Integer
     */
    function findDuplicate($nums) {
        $left = 1;
        $right = count($nums) - 1;
        while ($left < $right) {  // <
            $middle = ($left + $right) >> 1;
            $sum = 0;
            for ($j = 0; $j < count($nums); $j++) {
                if ($nums[$j] <= $middle) {
                    $sum++;
                }
            }
            if ($sum <= $middle) {  //排除掉中位数
                $left = $middle + 1;
            } else {   //不能排除中位数
                 $right = $middle;
            }
        }
        //相返回left还是right都可以  因为必然存在left==right
        return $left;
    }

这个模板第一步就是解除歧义,所有的判断都是 left

// $middle=floor(($l+$r)/2);
    // $middle=$l+floor(($r-$l)/2);
   // $middle=$l+(($r-$l)>>1); 
   // $middle = ($l + $r) >> 1;
  //上面的结果都是一样的,只是在数值大的情况下值会溢出


[1,3,6,9,12]  奇数的时候中位数没有争议直接是6 (0+4)/2 位置2=6

[1,3,6,9] 如果是($l + $r) >> 1 最终取的是左中位3  也就是位置1

[1,3,6,9] 想要右中位那么($l + $r + 1) >> 1 取6

取左中位还是右中位重要吗?很重要!!!!稍不留神,你的程序就是死的。这里有个窍门就是如果你选择的是左中位数,那么在左边界语句缩边的同时一定要把中位数排除,为什么,道理很简单

[1,3,6,9] 
  就像现在这样
  // $middle = ($l + $r) >> 1; 此时中位数是3
if(排除中位数的语句){
   $right = $miidle+1
}else{
   $left=$middle;
}
 //这时候对于如果走进的是else,就是灾难,因为left并不收缩,
 //这时候就进入了死循

所以你可以看到上面解题,我选择的是左中位数,我的左边界语句可以排除左中位数,就没问题。所以如果中位数选择的是左中位数,并且程序的左边界并不能排除中位数,那么这时候就应该选择右中位数,反之也是同样的道理。为了不吹牛,我拿出一个选右中位数的例子。为了省事直接拿的 Leetcode china 上的。


1668570768081.jpg


这道题二分查找按照上面规则的解。


/**
     * @param Integer $x
     * @return Integer
     */
    function mySqrt($x)
   {
        $left  = 0;
        $right = $x;
        while ($left < $right) {
            $middle = ($left + $right + 1) >> 1;
            //      $middle=($left+$right)>>1;
            if ($middle * $middle > $x) {
                $right = $middle - 1;
            } else {
                $left = $middle;
            }
        }
        return $left;
    }

注意看这里我选择了右中位数。下面我们来解释一下如果选择左中位数会咋么样。道理解释起来也很简单,按照题目的意思,一个数的平方大于目标数,那么它一定不会是目标数的平方根 (此时右边界语句一定能排除掉中位数!),反之,如果一个数的平方小于等于目标数,那么平方根可能就是中位数 (并不能把左中位数排除),左边界语句不能排除中位数,那么此时程序必要进入死循环,所以这时候我们需要取右中位数。


所以什么时候知道取哪边呢,也很简单,你可以先随便取左或者右,在收缩边界的时候,第一个语句直接写能排除中位数的,那么另一个边界一定不能排除中位数。然后再去判断取的那边中位数在边界的条件下是否能收缩空间,如若不能,反向获取中位数。当然这里的二分场景并不是很复杂,到了更复杂的二分场景,可能还需要做更多的操作。


最后再来一道


1668570784911.jpg

这道题稍微加了一点游戏难度,当然我可没说用暴力破解法,也不说解题思路了,直接贴按照上面说的模板解题代码

/**
     * @param Integer[][] $matrix
     * @param Integer $target
     * @return Boolean
     */
    function searchMatrix($matrix, $target)
    {
        $m = count($matrix);
        if ($m == 0) return false;
        $n = count($matrix[0]);
        if ($n == 0) return false;
        $left = 0;
        $right = $m * $n - 1;
        while ($left < $right) {
            $middle = ($left + $right) >> 1;
            $res = $matrix[$middle / $n][$middle % $n];
            if ($res < $target) {
                $left = $middle + 1;
            } else {
                $right = $middle;
            }
        }
        return $matrix[$left / $n][$left % $n] == $target ? true : false;
    }

最后附上这个二分查找思路的地址值得一看: 十分好用的二分查找模板


相关文章
|
PyTorch 算法框架/工具
Pytorch学习笔记(三):nn.BatchNorm2d()函数详解
本文介绍了PyTorch中的BatchNorm2d模块,它用于卷积层后的数据归一化处理,以稳定网络性能,并讨论了其参数如num_features、eps和momentum,以及affine参数对权重和偏置的影响。
1898 0
Pytorch学习笔记(三):nn.BatchNorm2d()函数详解
|
9月前
|
人工智能 自然语言处理 算法
0元!使用魔搭免费算力,基于Qwen基座模型,复现DeepSeek-R1
0元!使用魔搭免费算力,基于Qwen基座模型,复现DeepSeek-R1
299 7
|
存储 Ubuntu 关系型数据库
在Ubuntu 14.04上安装Bacula服务器的方法
在Ubuntu 14.04上安装Bacula服务器的方法
188 0
|
消息中间件 Linux RocketMQ
在Red Hat Enterprise Linux 9上使用Docker快速安装并部署
通过以上步骤,你可以在Red Hat Enterprise Linux 9上使用Docker快速安装并部署RocketMQ。这种方法不仅简化了安装过程,还提供了一个灵活的环境来管理和扩展消息队列系统。RocketMQ作为一款高性能的分布式消息系统,通过Docker可以实现快速部署和高效管理。
323 2
makefile 变量的替换,嵌套引用,命令行变量
makefile 变量的替换,嵌套引用,命令行变量
351 1
|
JSON 数据可视化 API
玩转数据科学:Python实战分析天气预报变动趋势
【10月更文挑战第1天】随着气候变化对日常生活的影响日益显著,理解和预测天气模式变得越来越重要。本文将引导您如何使用Python来抓取和分析天气预报数据,从而揭示天气变化的趋势。我们将介绍从获取公开气象API的数据到清洗、处理以及可视化整个过程的技术方法。
860 2
|
机器学习/深度学习
|
存储 NoSQL atlas
2024年向量数据库推荐榜单之MongoDB
目前市面上有哪些向量数据库解决方案,可协助您存储和检索高维向量?在推荐优选的几款向量数据库和库之前,我们需要厘清以下这两种技术的差异。
4938 0
|
机器人 定位技术 C++
技术笔记:ROS中测试机器人里程计信息
技术笔记:ROS中测试机器人里程计信息
|
项目管理
如何定义和创建项目基线?项目管理工具的详细指南
项目基线是项目管理中衡量进度的关键工具,它提供了一个基准来比较实际与计划进度。通过项目管理软件如Zoho Projects,可在甘特图中设定里程碑视图来创建基线,以便清晰展示任务进展差异,协助团队调整资源和计划,确保项目按目标顺利进行。当任务延误时,基线能显示原始与更新时间的对比,帮助解决问题。在Zoho Projects中,最多可创建6条基线进行对比分析。
386 0