精华内容
下载资源
问答
  • 二分查找的基本图解, 主要是学习二分查找的基本使用方法
  • 二分查找图解

    千次阅读 2017-09-18 20:04:47
  • 二分查找】详细图解

    万次阅读 多人点赞 2021-04-24 15:59:45
    二分查找 文章目录二分查找1. 简介2. 例子3. 第一种写法(左闭右闭)3.1 正向写法(正确演示)3.2 反向写法(错误演示)4. 第二种写法(左闭右开)4.1 正向写法(正确演示)4.2 反向写法(错误演示)5. 总结 写在前面...

    在这里插入图片描述

    二分查找

    写在前面:

    (一)二分法的思想十分容易理解,但是二分法边界处理问题大多数人都是记忆模板,忘记模板后处理边界就一团乱(👁:“我懂了”, ✋ :"你懂个🔨"​)因为我此前也是记忆模板,所以现在想通过一边学习,一边将所学记录成博客教出去(费曼学习法),希望以后能自己推导出边界如何处理,而不仅仅是记忆模板。欢迎一起交流学习,如有看法不一样之处也欢迎留言一起讨论!

    (二)我主要解释了二分法的左闭右闭区间,左闭右开区间两种写法,并且每个写法都举了相应的反例,范围写错的话可能会出现的错误等…

    1. 简介

    故事分享🏬:

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

    保安怎么知道只有一本书📖没有登记出借,万一全部都没有登记呢​?

    这个故事其实说出了二分查找需要的条件

    • 用于查找的内容逻辑上来说是需要有序的
    • 查找的数量只能是一个,而不是多个

    比如在一个有序的数组并且无重复元素的数组中,例如[1, 2, 3, 4, 5, 6],需要查找3的位置就可以使用二分查找。

    在二分查找中,目标元素的查找区间的定义十分重要,不同的区间的定义写法不一样

    因为查找的区间是不断迭代的,所以确定查找的范围十分重要,主要就是左右区间的开和闭的问题,开闭不一样,对应的迭代方式也不一样,有以下两种方式:

    • 左闭右闭[left, right]

    • 左闭右开[left, right)

    2. 例子

    这是一个使用二分查找的例题

    题目如下:

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

    示例一:

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

    示例二:

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

    提示:

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

    出自704. 二分查找 - 力扣(LeetCode) (leetcode-cn.com)

    二分法的思想很简单,因为整个数组是有序的,数组默认是递增的。

    • 首先选择数组中间的数字和需要查找的目标值比较
    • 如果相等最好,就可以直接返回答案了
    • 如果不相等
      • 如果中间的数字大于目标值,则中间数字向右所有数字都大于目标值,全部排除
      • 如果中间的数字小于目标值,则中间数字向左所有数字都小于目标值,全部排除

    二分法就是按照这种方式进行快速排除查找的

    tips:

    不用去纠结数组的长度是奇数或者偶数的时候,怎么取长度的一半,以下说明,可以跳过。

    当数组的长度为奇数的时候

    是奇数的情况很简单,指向中间的数字很容易理解,如果需要查找的数字为29

    因为29大于中间的数字大于11,所以左边的所有数字全部排除

    当数组的长度为偶数的时候

    这个时候中间的数字两边的数字数量就不一样了(刚开始学习二分法的时候我经常纠结这个问题,和另外一个长度除2得到的是最中间的数吗的问题,我相信不止我一个人纠结过……但其实这是同一个问题,每次长度除2,如果长度为奇数,得到的中间的数字两边数字数量相同,如果长度为偶数就为上图中间的数字两边的相差为 1)

    但是千万不要一直纠结中间的数字两边的数字数量不一样这个问题,因为:

    • 两边数量不一样是一定会出现的情况
    • 但是这种情况并不影响我们对中间数字和目标数字大小关系的判断
      • 只要中间数字大于目标数字,就排除右边的
      • 只要中间数字小于目标数字,就排除左边的

    所以数组长度是偶数还是奇数这个真的不重要,不影响怎么排除的问题,无非是多排除一个数字或者少排除一个数字

    • 真正影响的是中间那个数字到底该不该加入下一次的查找中,也就是边界问题

    3. 第一种写法(左闭右闭)

    二分法最重要的两个点:

    • while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right
    • 迭代过程中 middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle

    3.1 正向写法(正确演示)

    第一种写法:每次查找的区间在[left, right](左闭右闭区间),根据查找区间的定义(左闭右闭区间),就决定了后续的代码应该怎么写才能对。因为定义 target 在[left, right]区间,所以有如下两点:

    • 循环条件要使用while(left <= right),因为当(left == right)这种情况发生的时候,得到的结果是有意义的
    • if(nums[middle] > target) , right 要赋值为 middle - 1, 因为当前的 nums[middle] 一定不是 target ,需要把这个 middle 位置上面的数字丢弃,那么接下来需要查找范围就是[left, middle - 1]

    代码如下:

    int search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
    {
        int left = 0;
        int right = size - 1;	// 定义了target在左闭右闭的区间内,[left, right]
        while (left <= right) {	//当left == right时,区间[left, right]仍然有效
            int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
            if (nums[middle] > target) {
                right = middle - 1;	//target在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1;	//target在右区间,所以[middle + 1, right]
            } else {	//既不在左边,也不在右边,那就是找到答案了
                return middle;
            }
        }
        //没有找到目标值
        return -1;
    }
    

    下面图解算法的实现过程,建议将代码复制到一个文本编辑器中,边看代码边看图。或者我直接准备了图片,保存下来打开看就好了。

    首先看一个数组,需要对这个数组进行操作。需要对33进行查找的操作,那么target 的值就是33

    • 首先,对 left 的值和 right 的值进行初始化,然后计算 middle 的值
      • left = 0, right = size - 1
      • middle = (left + (right - left) / 2 )
    • 比较 nums[middle] 的值和 target 的值大小关系

      • if (nums[middle] > target),代表middle向右所有的数字大于target
      • if (nums[middle] < target),代表middle向左所有的数字小于target
      • 既不大于也不小于就是找到了相等的值
    • nums[middle] = 13 < target = 33,left = middle + 1

    • 见下图:

    • 循环条件为 while (left <= right)

    • 此时,left = 6 <= right = 11,则继续进行循环

    • 当前,middle = left + ((right - left) / 2),计算出 middle 的值

    • 计算出 middle 的值后,比较 nums[middle] 和 target 的值,发现:

      • nums[middle] = 33 == target = 33,找到目标

    3.2 反向写法(错误演示)

    对应第一种正向的写法,我们把循环条件修改看看会发生什么事

    • 原查找区间 [left, right]
    • 原循环条件是 while (left <= right)

    修改后题目对应的条件:

    • 查找区间不变,仍然是[left, right]
    • 查找数字为27 (target = 27)
    • 循环条件修改为while (left < left)

    代码:

    int search(int nums[], int size, int target) 
    {
        int left = 0;
        int right = size - 1;	
        while (left < right) {	//left <= right 修改为 left < right,其他不变
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else if (nums[middle] < target) {
                left = middle + 1;
            } else {	
                return middle;
            }
        }
        //没有找到目标值
        return -1;
    }
    

    代码图片,边看模拟过程边看代码哦!

    好了,现在开始用图片模拟过程

    • 初始化一个数组,计算 middle 的值
    • 根据计算的 middle 值确定 nums[middle]
    • 因为nums[middle] = 13 < target = 27,所以left = middle + 1
    • 继续计算 middle 的值
    • 因为 nums[middle] = 33 > target = 27,所以 right = middle - 1
    • 接着计算 middle 的值
    • 因为 nums[middle] = 22 < target = 27,此时 left = middle + 1,此时 left = right,而循环条件为while (left < right),所以还未找到27 的情况下算法就跳出了循环,返回 -1

    4. 第二种写法(左闭右开)

    4.1 正向写法(正确演示)

    第二种写法:每次查找的区间在 [left, right),(左闭右开区间), 根据区间的定义,条件控制应该如下:

    • 循环条件使用while (left < right)
    • if (nums[middle] > target), right = middle,因为当前的 nums[middle] 是大于 target 的,不符合条件,不能取到 middle,并且区间的定义是 [left, right),刚好区间上的定义就取不到 right, 所以 right 赋值为 middle。

    代码如下:

    int search(int nums[], int size, int target)
    {
    	int left = 0;
    	int right = size; //定义target在左闭右开的区间里,即[left, right)
    	while (left < right) {	//因为left = right的时候,在[left, right)区间上无意义
    		int middle = left + ((right - left) / 2);
    		if (nums[middle] > target) {
    			right = middle; //target 在左区间,在[left, middle)中 
    		} else if (nums[middle] < target) {
    			left = middle + 1;
    		} else {
    			return middle;
    		}
    	} 
        // 没找到就返回-1
    	return -1;
    }
    

    代码图片:保存下来边看代码边看图片演示过程

    • 需要查找的值为3

    第一步是初始化 left 和 right 的值,然后计算 middle 的值

    • left = 0, right = size
    • 循环条件while (left < right)

    因为是左闭右开区间,所以数组定义如下:

    • 计算 middle 的值,
    • 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 22 > target = 3
    • 所以 right = middle
    • 符合循环的条件,接着计算 middle 的值
    • 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 9 > target = 3
    • 所以 right = middle
    • 符合循环的条件,继续计算 middle 的值
    • 比较 nums[middle] 和 target 的大小关系:因为nums[middle] = 0 < target = 3
    • 所以 left = middle + 1

    在这里插入图片描述

    • 符合循环条件,接着计算 middle 的值
    • 比较 nums[middle] 和 target 的关系:nums[middle] = 3 == target = 3
    • 成功找到 target

    4.2 反向写法(错误演示)

    对应第二种正确的写法,照样把循环条件修改,看会发生什么事

    正确的写法中条件为:

    • 查找原区间 [left, right)
    • 循环条件为 while (left < right)

    修改后题目对应的条件:

    • 查找区间不变,仍然是 [left, right)

    • 循环条件修改为:while (left <= right)

    • 查找的数字为26(数组中不存在这个数字!!!

    代码:

    int search(int nums[], int size, int target)
    {
    	int left = 0;
    	int right = size; 
    	while (left <= right) {	//条件left < right 修改为 left <= right
    		int middle = left + ((right - left) / 2);
    		if (nums[middle] > target) {
    			right = middle; 
    		} else if (nums[middle] < target) {
    			left = middle + 1;
    		} else {
    			return middle;
    		}
    	} 
        // 没找到就返回-1
    	return -1;
    }
    

    代码图片:(记得边看边保存图片代码边看图片演示哦!)

    以下是演示全过程:

    • 同样,开始初始化一个数组
    • 先计算 middle 的值
    • 判断 nums[middle] 和 target 的大小关系:nums[middle] = 22 < target = 26
    • left = middle + 1 (其实这里nums[left] 已经等于27,26不可能找到,接下去就看算法是否能够知道数组中不存在26并且返回-1 了)
    • 符合循环条件,计算 middle 的值
    • 判断 nums[middle] 和 target 的大小关系:nums[middle] = 57 > target = 26
    • right = middle
    • 满足循环条件,接着计算 middle 的值
    • 比较 nums[middle] 和 target 的大小关系:nums[middle] = 33 > target = 26
    • right = middle
    • 符合循环条件,继续计算 middle 的值
    • 比较 nums[middle] 和 target 大小关系,因为 nums[middle] = 27 > target = 26,
    • 所以 right = middle,自此,left 和 right 相遇,但是循环条件被我们修改成 while (left <= right) 会接着做循环
    • 接下来就是死循环

    • 因为 middle = left + ((right - left) / 2),当 left = right 的时候,middle 的值不会继续改变

    • middle 不继续改变,由于right = middle,right 也不会改变,所以三个数字自此开始不会继续改变

    • 循环条件 while (left <= right) 却仍然满足,不会跳出循环

    • 死循环……

    5. 总结

    二分法最重要的两个点,就是循环条件和后续的区间赋值问题

    因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题

    所以循环条件和赋值问题必须统一,也就是循环不变量。

    相关题目推荐:

    本文相关信息:

    • 算法学习自微信公众号:“代码随想录”
    • 画图软件:Diagrams
    • 代码生成图片软件:Carbon

    ❤️完结撒花❤️

    展开全文
  • Java 二分查找图解

    2020-03-04 10:00:50
    本文介绍了 Java 二分查找图解的相关内容。。。


    二分查找

    1. 普通查找和二分查找

    a. 普通查找

    • 原理:遍历数组,获取每一个元素,然后判断当前遍历的元素是否和要查找的元素相同,如果相同就返回该元素的索引;
    • 如果没有找到,就返回一个负数作为标识(一般是 -1);

    b. 二分查找

    • 原理: 每一次都去获取数组的中间索引所对应的元素,然后和要查找的元素进行比对,如果相同就返回索引;
    • 如果不相同,就比较中间元素和要查找的元素的值;
    • 如果中间元素的值大于要查找的元素,说明要查找的元素在左侧,那么就从左侧按照上述思想继续查询(忽略右侧数据);
    • 如果中间元素的值小于要查找的元素,说明要查找的元素在右侧,那么就从右侧按照上述思想继续查询(忽略左侧
      数据);
    • 二分查找对数组是有要求的,数组必须已经排好序;
    • 时间复杂度:二分搜索每次把搜索区域砍掉一半,很明显时间复杂度为 O ( l o g n ) O(log n) O(logn)
    • 空间复杂度: O ( 1 ) O(1) O(1),虽递归形式定义,但是尾递归,可改写为循环;
    • 应用:Arrays.binarySearch(int[] arr, target);

    2. 图解

    图解


    3. Java 实现

    public class Test {
        public static void main(String[] args) {
            int[] arr = {10, 14, 21, 38, 45, 47, 53, 81, 87, 99};
            int index = binarySearch(arr, 38);
            System.out.println(index);
        }
    
        /**
         * 二分查找方法
         *
         * @param arr    查找的目标数组
         * @param target 查找的目标值
         * @return 找到的索引, 如果没有找到返回-1
         */
        public static int binarySearch(int arr[], int target) {
            //开始和结束索引
            int left = 0;
            int right = arr.length - 1;
            //循环
            while (left <= right) {
                //获取中间索引
                int middle = (left + right) / 2;
                //比较中间索引的元素和target
                if (arr[middle] == target) {
                    return middle;// key found
                } else if (arr[middle] < target) {
                    left = middle + 1;
                } else {
                    right = middle - 1;
                }
            }
            return -1;//key not found,则返回-1
            //return -(left + 1);;//注意:Arrays类中返回的是(length+1)或-(endIndex+1)
        }
    }
    /*
    输出
    3
     */
    
    展开全文
  • 本文就来探究几个最常用的二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。 而且,我们就是要深入细节,比如while循环中的不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些...

    本文就来探究几个最常用的二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。

    而且,我们就是要深入细节,比如while循环中的不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。

    一、二分查找的框架

    int binarySearch(int[] nums, int target) {
        int left = 0, right = ...;
    
        while(...) {
            int mid = (right + left) / 2;
            if (nums[mid] == target) {
                ...
            } else if (nums[mid] < target) {
                left = ...
            } else if (nums[mid] > target) {
                right = ...
            }
        }
        return ...;
    }

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

    其中...标记的部分,就是可能出现细节问题的地方,当你见到一个二分查找的代码时,首先注意这几个地方。后文用实例分析这些地方能有什么样的变化。

    另外声明一下,计算 mid 时需要技巧防止溢出,建议写成: mid = left + (right - left) / 2,本文暂时忽略这个问题。

    二、寻找一个数(基本的二分搜索)

    这个场景是最简单的,可能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。

    int binarySearch(int[] nums, int target) {
        int left = 0; 
        int right = nums.length - 1; // 注意
    
        while(left <= right) { // 注意
            int mid = (right + left) / 2;
            if(nums[mid] == target)
                return mid; 
            else if (nums[mid] < target)
                left = mid + 1; // 注意
            else if (nums[mid] > target)
                right = mid - 1; // 注意
            }
        return -1;
    }

    1. 为什么 while 循环的条件中是 <=,而不是 < ?

    答:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。

    这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。

    我们这个算法中使用的是 [left, right] 两端都闭的区间。这个区间就是每次进行搜索的区间,我们不妨称为「搜索区间」(search space)

    什么时候应该停止搜索呢?当然,找到了目标值的时候可以终止:

     if(nums[mid] == target)
            return mid; 

    但如果没找到,就需要 while 循环终止,然后返回 -1。那 while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。

    while(left <= right)的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候搜索区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。

    while(left < right)的终止条件是 left == right,写成区间的形式就是 [right, right],或者带个具体的数字进去 [2, 2],这时候搜索区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就可能出现错误。

    当然,如果你非要用 while(left < right) 也可以,我们已经知道了出错的原因,就打个补丁好了:

    //...
    while(left < right) {
        // ...
    }
    return nums[left] == target ? left : -1;

    2. 为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid,没有这些加加减减,到底怎么回事,怎么判断?

    答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。

    刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,如何确定下一步的搜索区间呢?

    当然是去搜索 [left, mid - 1] 或者 [mid + 1, right] 对不对?因为 mid 已经搜索过,应该从搜索区间中去除。

    3. 此算法有什么缺陷?

    答:至此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。

    比如说给你有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。

    这样的需求很常见。你也许会说,找到一个 target 索引,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的时间复杂度了。

    我们后续的算法就来讨论这两种二分查找的算法。

    三、寻找左侧边界的二分搜索

    直接看代码,其中的标记是需要注意的细节:

    int left_bound(int[] nums, int target) {
        if (nums.length == 0) return -1;
        int left = 0;
        int right = nums.length; // 注意
    
        while (left < right) { // 注意
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                right = mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid; // 注意
            }
        }
        return left;
    }

    1. 为什么 while(left < right) 而不是 <= ?

    答:用相同的方法分析,因为初始化 right = nums.length 而不是 nums.length - 1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。

    while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 恰巧为空,所以可以正确终止。

    2. 为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

    答:因为要一步一步来,先理解一下这个「左侧边界」有什么特殊含义:

    对于这个数组,算法会返回 1。这个 1 的含义可以这样解读:nums 中小于 2 的元素有 1 个。

    比如对于有序数组 nums = [2,3,5,7], target = 1,算法会返回 0,含义是:nums 中小于 1 的元素有 0 个。如果 target = 8,算法会返回 4,含义是:nums 中小于 8 的元素有 4 个。

    综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间 [0, nums.length],所以我们简单添加两行代码就能在正确的时候 return -1:

    while (left < right) {
        //...
    }
    // target 比所有数都大
    if (left == nums.length) return -1;
    // 类似之前算法的处理方式
    return nums[left] == target ? left : -1;

    3. 为什么 left = mid + 1,right = mid ?和之前的算法不一样?

    答:这个很好解释,因为我们的「搜索区间」是 [left, right) 左闭右开,所以当 nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。

    4. 为什么该算法能够搜索左侧边界?

    答:关键在于对于 nums[mid] == target 这种情况的处理:

     if (nums[mid] == target)
            right = mid;

    可见,找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。

    5. 为什么返回 left 而不是 right?

    答:返回left和right都是一样的,因为 while 终止的条件是 left == right。

    四、寻找右侧边界的二分查找

    寻找右侧边界和寻找左侧边界的代码差不多,只有两处不同,已标注:

    int right_bound(int[] nums, int target) {
        if (nums.length == 0) return -1;
        int left = 0, right = nums.length;
    
        while (left < right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                left = mid + 1; // 注意
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            }
        }
        return left - 1; // 注意

    1. 为什么这个算法能够找到右侧边界?

    答:类似地,关键点还是这里:

     if (nums[mid] == target) {
            left = mid + 1;

    当 nums[mid] == target 时,不要立即返回,而是增大「搜索区间」的下界 left,使得区间不断向右收缩,达到锁定右侧边界的目的。

    2. 为什么最后返回 left - 1 而不像左侧边界的函数,返回 left?而且我觉得这里既然是搜索右侧边界,应该返回 right 才对。

    答:首先,while 循环的终止条件是 left == right,所以 left 和 right 是一样的,你非要体现右侧的特点,返回 right - 1 好了。

    至于为什么要减一,这是搜索右侧边界的一个特殊点,关键在这个条件判断:

    if (nums[mid] == target) {
            left = mid + 1;
            // 这样想: mid = left - 1

    因为我们对 left 的更新必须是 left = mid + 1,就是说 while 循环结束时,nums[left] 一定不等于 target 了,而 nums[left - 1]可能是target。

    至于为什么 left 的更新必须是 left = mid + 1,同左侧边界搜索,就不再赘述。

    3. 为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

    答:类似之前的左侧边界搜索,因为 while 的终止条件是 left == right,就是说 left 的取值范围是 [0, nums.length],所以可以添加两行代码,正确地返回 -1:

    while (left < right) {
        // ...
    }
    if (left == 0) return -1;
    return nums[left-1] == target ? (left-1) : -1;

    五、最后总结

    先来梳理一下这些细节差异的因果逻辑:

    第一个,最基本的二分查找算法:

    因为我们初始化 right = nums.length - 1
    所以决定了我们的「搜索区间」是 [left, right]
    所以决定了 while (left <= right)
    同时也决定了 left = mid+1 和 right = mid-1
    
    因为我们只需找到一个 target 的索引即可
    所以当 nums[mid] == target 时可以立即返回

    第二个,寻找左侧边界的二分查找:

    因为我们初始化 right = nums.length
    所以决定了我们的「搜索区间」是 [left, right)
    所以决定了 while (left < right)
    同时也决定了 left = mid+1 和 right = mid
    
    因为我们需找到 target 的最左侧索引
    所以当 nums[mid] == target 时不要立即返回
    而要收紧右侧边界以锁定左侧边界

    第三个,寻找右侧边界的二分查找:

    因为我们初始化 right = nums.length
    所以决定了我们的「搜索区间」是 [left, right)
    所以决定了 while (left < right)
    同时也决定了 left = mid+1 和 right = mid
    
    因为我们需找到 target 的最右侧索引
    所以当 nums[mid] == target 时不要立即返回
    而要收紧左侧边界以锁定右侧边界
    
    又因为收紧左侧边界时必须 left = mid + 1
    所以最后无论返回 left 还是 right,必须减一

    如果以上内容你都能理解,那么恭喜你,二分查找算法的细节不过如此。

    通过本文,你学会了:

    1. 分析二分查找代码时,不要出现 else,全部展开成 else if 方便理解。

    2. 注意「搜索区间」和 while 的终止条件,如果存在漏掉的元素,记得在最后检查。

    3. 如需要搜索左右边界,只要在 nums[mid] == target 时做修改即可。搜索右侧时需要减一。

    就算遇到其他的二分查找变形,运用这几点技巧,也能保证你写出正确的代码。LeetCode Explore 中有二分查找的专项练习,其中提供了三种不同的代码模板,现在你再去看看,很容易就知道这几个模板的实现原理了。

    展开全文
  • 【算法图解】 之 [二分查找法] 详解

    千次阅读 多人点赞 2019-07-22 16:07:37
    入门算法学习,看的第一本是深入浅出的《算法图解》一书,本博客是对《算法图解》一书的学习笔记,将书中的分享的算法示例用Python3语言实现。 ...或者也可以留言你的邮箱...二分查找是一种算法,其输入是一个有序的元...
  • 二分搜索技术 二分搜索算法是运用分治策略的典型例子。 分治分治顾名思义就是分而治之。基本思想是将一个规模比较大的问题分为多个小问题,这些问题相互独立且与原问题相同。递归的解这些问题,最后将这些问题的解...
  • 二分查找算法图解

    千次阅读 2019-02-24 15:27:07
    使用二分查找算法的前提:必须是一个有序的数组 假如有这么一个需求:定义一个函数接收一个数组对象和一个要查找的目标元素,函数要返回该目标元素在 数组中的索引值,如果目标元素不存在数组中,那么返回-1表示。 ...
  • 假设要在电话簿中找一个名字以K打头的人,(现在谁还用电话簿!)可以从头开始翻页,直到进入以K打头的部分。但你很可能不这样做,而是从中间开始,因为你知道以K打头的名字在电话簿中间。...二分查找是一种算
  • 顺序有序表进行二分查找

    千次阅读 2020-10-25 20:33:53
    设置low在有序表最左边,high在最右边,mid为(low+high)/2;取中间位置, 当mid的值等于k,返回第mid+1个(从0开始) 当mid的值大于k,从mid左边开始找,令high=mid-1; 当mid的值小于k,从mid右边开始找,令low=mid+...
  • 二分查找原理图解 拿升序数组为例子进行解释,假设我们需要再数组中找到目标值key的下标。 1、首先可以将数组按key所在下标划分为三个部分,左边是小于key的部分,然后中间是key值,右边为大于key的部分。 用left...
  • 二分查找及其Python实现【算法图解】1. 引出问题2. 简单查找3. 二分查找(折半查找)4. 运行时间对比5. 二分查找的Python实现6. 总结 1. 引出问题 做一个游戏,我随便想一个整数数字,这个数字在【1-100】范围内,你的...
  • 算法图解1.2-二分查找

    2020-06-02 23:45:31
    二分查找是一种算法,其输入的是一个有序的元素列表。如果查找的元素,包含在列表中,二分查找返回其位置,否则返回null 比如查找1-100之间的数字,第一中使用简单查找,那么只能依次按照顺序进行查找每个数字,...
  • 查找是在大量的信息中寻找一个特定的信息元素,在某个容器中找到某数据所使用的算法,就是查找算法。 一、线性查找 给定一个查找值,在查找的容器中逐个查找给定值。 线性查找是最简单的查找算法。 代码实现: 创建...
  • 导语:算法之路–二分查找 作者:变优秀的小白,Click 进入主页 爱好:Americano More Ice ! 算法上集: 算法图解(一) 数组与链表 前提知识 大家是否还记得对数呢?你很可能不记得什么是对数了,但你大几率记得什么...
  • 这道题也是用了二分查找解决的,但它跟正常的二分查找在细节上有些不同,我们在正常的二分查找基础上稍作修改就可以解决这道题了。 二分查找的思路很简单,但是具体实现的时候,一不小心可能就会出错。 下面我们先看...
  • 举一个例子,我们使用二分查找在电话簿中两个实业公司名,找到了第一个并返回了位置,没有找到第二个,所以反悔了NULL: 1)简单查找 下面举一个例子,来说明了二分查找的工作原理。这是一个酒桌上常玩的游戏...
  • 文章目录二分查找的接口定义二分查找的实现与分析示例:二分查找的实现二分查找的例子:拼写检查器示例:拼写检查器的头文件 现在我们来玩一个猜数的游戏,假设有一个人要我们猜0-99之间的一个数。那么最好的方法...
  • 折半查找算法是对于有序的序列而言的,每次查找后折半,大概缩短了一半的查找区间,是一种效率较高的查找算法。 要求: list必须是顺序结构,且按照关键词大小进行有序排列。 思路: 在有序序列中查找元素,每次取...
  • 图解算法第一章二分查找使用PHP实现,仅供参考,错误之处,欢迎联系我纠正 function binary_search ( $list , $x ) { $start = 0 ; $end = count( $list ) - 1 ; while ( $start $end ) ...
  • 二分查找代码和图解

    2020-07-24 16:12:04
    二分查找也叫折半查找,每次可以去掉一半的查找范围,从而提高查找的效率;使用时要求数组按从小到大的顺序排列; 二分查找代码 public class Text03 { public static void main(String[] args) { int num=10; int[]arr=...
  • 在讲折半插入前先讲一下二分查找把 现在有一个给定的数组让你找看某个数在不在该数组中,最笨的方法就是写个For循环从数组的头循环到数组的尾巴,找到了就返回该数并break。但是如果数组很大怎么办?这个方法显然会...
  • 有四个数,假设我们要查找4,那么就先从第个数字2开始,然后4比较2与的大小,如果小了就在[3,4]中寻找,大了就在[1]中寻找。然后再在剩下的数中,取中间的数查找比较。 2、适用范围:有序列表 3、最大寻找次数:...
  • 二分查找(算法图解

    千次阅读 2018-03-27 14:44:46
    算法图解中的二分查找 缺陷:测试的数组数据是有序的,从小到大排列 python 3.5 def binaery_search(list,item): low=0 high=len(list)-1 while low &lt;= high: mid=int((low+high)/2) guess=list...
  • 二分查找——递归

    2021-11-18 16:43:00
    二分查找又称折半查找,是对一个有序的列表通过按中间元素切分搜索空间进行快速查找的,也可以通过递归方法实现, 题目1 给定一个整数数组ribbons和一个整数k,数组每项ribbons[i]表示第i条绳子的长度。对于每条...
  • 普通查找 和 二分查找 的区别: 普通查找:首先要遍历数组,获取每个元素,判断当前遍历的元素是否和要查找的元素相 同,相同的话就返回该元素的索引,没找到,手动返回-1 二分查找:每一次都要去获取数组的...
  • 二分查找

    2020-12-28 22:53:17
    二分查找法(Binary Search)算法,也叫折半查找算法。二分查找针对的是一个有序的数据集合,查找思想有点类似于分治思想。每次都通过跟区间的中间元素对比,将带查找的区间缩小为之前的一半,直到找到要查找的元素...
  • 算法图解二分查找

    千次阅读 2018-08-04 00:44:00
     利用二分查找每次取中间数 有小数可以向上或向下取整数  100以内的数字 最多7次可以找出来  普通查找:  而普通查找则是从头到位遍历最好的情况是1 一次找出  最差是100次   二分查找随着元素的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,799
精华内容 4,719
关键字:

二分查找图解

友情链接: Hal8188EPwrSeq.rar