LeetCode算法题:二分查找题解[java]

LeetCode算法题:二分查找题解[java]



前言

首先给大家看一则笑话,简单认识一下二分查找.

有一天阿东到图书馆借了 N 本书,出图书馆的时候,警报响了,于是保安把阿东拦下,要检查一下哪本书没有登记出借。阿东正准备把每一本书在报警器下过一下,以找出引发警报的书,但是保安露出不屑的眼神:你连二分查找都不会吗?于是保安把书分成两堆,让第一堆过一下报警器,报警器响;于是再把这堆书分成两堆…… 最终,检测了 logN 次之后,保安成功的找到了那本引起警报的书,露出了得意和嘲讽的笑容。于是阿东背着剩下的书走了。
从此,图书馆丢了 N - 1 本书。

其实二分查找看似不难,但其实并不简单,Knuth 大佬(发明 KMP 算法的那位)都说二分查找:思路很简单,细节是魔鬼。虽然这里面确实存在整形溢出的bug,但是二分查找真正的细节并不是在这,而是在于到底要给查找索引加1还是减1,循环条件到底是用<=还是<,只有正确理解这些细节,才算会写二分查找,否则的话,这二分查找写起来就是个玄学问题.


提示:以下是本篇文章正文内容,下面案例可供参考

一、真题

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999,9999]之间。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、个人Java实现步骤

部分思路已经写在代码注释里面了,可选择性观看,注释里面标有细节的地方,我在下方会单独解释.

import java.util.Arrays;

public class TwoSele {
    public static void main(String[] args) {
        int[] numbers={22,11,44,33,66,55,2,4,6,-1,-2};//定义一个数组
        Arrays.sort(numbers);//对数组进行排序
        int target=-1;//定义一个我们要去查找的数据
        int leftidx=0;//数组左边索引
        int rightidx=numbers.length-1;//数组右边的索引,从0开始,要做减一,防止溢出
        int flag=0;//判断是否查找到,0表示没找到,1表示查找到
        while (leftidx<=rightidx){//细节
            int mid=leftidx+(rightidx-leftidx)/2;//细节
            if (numbers[mid]==target){//假如正好等于,直接回显即可
                System.out.println("查到数据:"+numbers[mid]+"下标是:"+mid);
                flag+=1;//判断是否进来了
                break;
            }else if (numbers[mid]<target){
                leftidx=mid+1;//细节
            }else if (numbers[mid]>target){
                rightidx=mid-1;//细节
            }
        }
        if (flag==0){
            System.out.println("没找到"+-1);
        }
    }
}


三.解刨细节

分析二分查找的一个窍门是:不要出现 单独的else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。本文都会使用 else if,读者理解后可自行简化。

1.循环条件的细节分析

为什么 while 循环的条件中是 <=,而不是 <
答:

因为在我们初始化时, int rightidx=numbers.length-1;,右边区间是以计算机从0开始计数做判断,即我们的右区间是元素最后一个索引,代表最后一个值,如果索引1大小是numbers.length的话,那么索引是越界的.
这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left,
right). 我们这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间。
什么时候应该停止搜索呢?找到了目标值的时候就可以终止.

那如果没找到目标值呢?

如果没有找到目标值,那么我们循环下去最后搜索区间就是空的,搜索区间为空,就是没有找到目标值. while(leftidx<=rightidx)的终止条件是leftidx==rightidx+1,用区间形式表达就是[rightidx+1,rihtidx],用具体数字表达就是[3,2],那么3和2之间还有数字吗?可能有,但是就现在我们的日常生活当中是没有比3小,比2大的数字的,所以循环就会终止.

2.循环变量细节分析

为什么要这么去做区间:int mid=leftidx+(rightidx-leftidx)/2;

当初始化进入循环之后,rightidx就是数组最后一个元素的索引,除去一个2,就是把数组平均分成两份,一份在左,一份在右,因为我们做了排序,所以左边比右边元素小.
在当前索引mid的数据比我们要找的数据小的时候,会对左索引进行mid加一,意思是左边mid+1索引之前的元素一定不是我们要找的元素,所以把左边区域裁剪出来,即[mid+1,rightidx],在这个区间再去搜索.
那么在当前索引mid的数据比我们要找的数据大了怎么办?去对右边的数据做裁剪,即[leftidx,mid-1].
裁剪之后又对数据平均分成两份,即先在中间做判断,那么如此循环下去,一直找到最后一个元素.

为什么 left idx= mid + 1,rightidx = mid - 1?我看有的代码是 right = mid 或者 left = mid,为什么要用加减呢?
答:

这是二分查找的一个思维难点,不过只要理解了这些难点,就会很容易进行判断了.
刚才我们讲到了[搜索区间]的概念,因为两端搜索区间是闭环的,那当我们查到mid这个下标的数据不是我们要找的数据时,即[leftidx,mid],那我们这时候应该将这两个索引丢弃,并加一,因为下次再去搜索区间的时候我们还需要再去搜索mid这个区间吗,显然不需要,因为我们已经搜索过了,所以需要进行加一减一操作.

3.此算法有哪些缺陷

这个算法存在一定的局限性,比如给你一个有序数据nums[11,22,22,22,33],那么此算法在查到22的时候会直接跳出,造成数据不稳定现象.
那么有问题,肯定有解决方法,可以再去判断区间边界,返回一个数组.

四.小结

本章概述了二分查找的实现步骤,以及二分查找的细节分析,推荐初学者,先去理解细节,再去写算法实现,这个算法最厉害的点就在于细节的把握,如果不掌握好细节,直接上手练的话,很容易走不出来,先理解,再实现.
有哪里不足或者有更好的建议,欢迎留言吐槽,有哪里不懂的小伙伴,可以私信我,我会一一答复,感谢认可,感谢支持!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道而起

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值