前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >有了这个通用公式,再也不怕面试考二分法

有了这个通用公式,再也不怕面试考二分法

作者头像
你好戴先生
发布2022-12-06 17:01:36
1820
发布2022-12-06 17:01:36
举报
文章被收录于专栏:戴言泛滥戴言泛滥

大家好,我是戴先生

#前言

二分法是面试中考察频率极高的一个算法

说它简单是因为思路很清晰,就是折半查找对比

说它复杂是因为变形很多,极端情况容易考虑不到

二分法最难的地方其实就是边界的处理

前期对二分法的一些变形做了部分梳理

可以先对比看一下,以便能更好地理解下边的通用公式

必会算法:在旋转有序的数组中找最小值

必会算法:在旋转有序的数组中搜索

高频面试题:找出峰值元素

算法题:切木头

今天对二分法做一个总结

分享给大家一个易理解又简单的通用公式

保证从此再也不怕面试考察二分法了

#通用公式

直接上通用公式代码:

代码语言:javascript
复制
    public static int searchByBinary(int[] num, int target) {
        if (num == null || num.length == 0) {
            return -1;
        }

        int start = 0;
        int end = num.length - 1;
        int mid;
        while (start + 1 < end) {
            mid = start + (end - start) / 2;
            if (num[mid] == target) {
                return mid;
            } else if (num[mid] > target) {
                end = mid;
            } else {
                start = mid;
            }
        }
        if (num[start] == target) {
            return start;
        }
        if (num[end] == target) {
            return end;
        }
        return -1;
    }

注意:

前边也说了,二分法的关键其实就是边界的处理

处理不当就会出现死循环或者越界的问题

写法有很多,但是一般循环条件和循环体里边的赋值方式是需要对应的

比如也有很多写法是(start < end)和start = mid + 1 以及end = mid -1这样的组合

个人认为也可以,但不是太好理解

建议大家也不要贪多,记一种自认为容易理解的边界处理思路就可以了

#公式详解

##01

代码语言:javascript
复制
        if (num == null || num.length == 0) {
            return -1;
        }

这部分代码就不用说了

建议大家在平时写代码或者算法题的时候也都养成习惯

一定要先考虑入参为空的情况

##02

代码语言:javascript
复制
        int start = 0;
        int end = num.length - 1;
        int mid;

这部分就是常规操作了

拿到查找范围的头和尾

并定义一个中间值变量

##03

代码语言:javascript
复制
        while (start + 1 < end) {
            // ······
        }

关于这个循环跳出条件可能就会有很多人疑惑了

一般不是写(start < end)吗?

其实两者的唯一区别就是对边界的处理

前者在start和end相邻的时候是不会进入循环

当出现start和end相邻的时候,二分法的工作其实已经做完了

所以就只需要对这两个值做一个单独对比就行了

也就是下面这段代码

代码语言:javascript
复制
        if (num[start] == target) {
            return start;
        }
        if (num[end] == target) {
            return end;
        }

后者在start和end相邻的时候还是会进入while循环

这个时候在循环体内部就要同时对边界也进行统一的处理

比如start = 2,end = 3的情况

此时中间值mid = 2 = start

假如刚好target > num[2]

那么我们就要考虑start与mid的关系了

应该是start = mid + 1?还是start = mid?还是start = mid - 1?

再或者是if条件做单独的判断?

如果是逻辑思维不强的同学,可能就把自己绕晕了

##04

代码语言:javascript
复制
            mid = start + (end - start) / 2;

这段代码就是取中间值

个人认为这么写更好理解一些

当然这个就因人而异了

按照自己的习惯写mid = (start + end)/ 2也是一样的

##05

代码语言:javascript
复制
            if (num[mid] == target) {
                return mid;
            } else if (num[mid] > target) {
                end = mid;
            } else {
                start = mid;
            }

这段代码就是跟##03中的循环条件对应的

抛开start和end相邻的情况做的处理

因为绝对存在start ≠ mid ≠ end

所以边界问题的就很好处理了

思路就非常简单清晰了

##06

代码语言:javascript
复制
        if (num[start] == target) {
            return start;
        }
        if (num[end] == target) {
            return end;
        }

这部分代码就是对边界进行单独处理

循环体内只需要做通用的处理就行

边界问题单独处理更容易理解一些

##07

代码语言:javascript
复制
        return -1;

如果到了这一步就是没有没有目标值

直接返回特定值就行了

#举个栗子

接下来我们使用一些极端例子来考验一下这个通用公式

因为所有的例子最终都会简化成下边的极端情况

##极端情况01

代码语言:javascript
复制
int[] num = {1};
int target = 1;

此时代码根部就不会进入while循环

直接会走到对边界处理的代码

直接就返回结果index = 0

##极端情况02

代码语言:javascript
复制
int[] num = {1, 2};
int target = 1;

同样的,此时代码根部就不会进入while循环

直接会走到对边界处理的代码

直接就返回结果index = 0

##极端情况03

代码语言:javascript
复制
int[] num = {1, 2, 3};
int target = 1;

此时start = 0;end = 2

进入while循环后mid = 1

而num[mid = 1] = 2 > target = 1;

所以start = mid = 1

此时start = 1;end = 2

将不会进入循环,问题简化为##极端情况02

文/戴先生@2022年09月17日

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 你好戴先生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档