当前位置: 首页 > news >正文

【二分搜索】二分搜索代码模板

        下面这首算法小诗出自《labuladong的算法小抄》

二分搜索套路歌

二分搜索不好记,左右边界让人迷。小于等于变小于,mid加一又减一。

就算这样还没完,return应否再减一?信心满满刷力扣,通过比率二十一。

我本将心向明月,奈何明月照沟渠!labuladong从天降,一同手撕算法题。

管它左侧还右侧,搜索区间定乾坤。搜索一个元素时,搜索区间两端闭。

while条件带等号,否则需要打补丁。if相等就返回,其他的事甭操心。

mid必须加减一,因为区间两端闭。while结束就凉凉,凄凄惨惨返-1.

搜索左右边界时,搜索区间要阐明。左闭右开最常见,其余逻辑便自明:

while要用小于号,这样才能不漏掉。if相等别返回,利用mid锁边界。

mid加一或减一?要看区间开或闭。while结束不算完,因为你还没返回。

索引可能出边界,if检查保平安。左闭右开最常见,难道常见就合理?

labuladong不信邪,偏要改成两端闭。搜索区间记于心,或开或闭有何异?

二分搜索三变体,逻辑统一容易记。一套框架改两行,胜过千言和万语。

二分搜索框架

        先写一个二分搜索的框架,后面的几种二分搜索的变形都是基于这个代码框架:

    public int binarySearch(int[] nums, int target) {int left = 0, right = ...;int counter = 1;while (...) {int mid = left + (right - left) / 2;System.out.println("搜索了" + counter + "次,left = " + left + ",right = " + right + ",mid = " + mid);++counter;if (nums[mid] == target) {...} else if (nums[mid] > target) {right = ...;} else if (nums[mid] < target) {left = ...;}}return ...;}

        分析二分搜索的一个技巧是:不要出现 else,,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。其中 ... 标记的地方,就是可能出现细节问题的地方,当你看到一个二分搜索的代码时,首先要注意这几个地方。

        另外声明一下,计算 mid 时需要防止溢出。代码中 left + (right - left) / 2 和 (left + right) / 2 的结果相同。但是如果 left 和 right 太大,直接相加会导致整型溢出,而改写成前者则不会出现溢出。

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

        这个场景最简单:搜索一个数,如果存在,则返回其索引,否则返回-1。

    public int binarySearch(int[] nums, int target) {int left = 0, right = nums.length - 1; // 注意int counter = 1;while (left <= right) {int mid = left + (right - left) / 2;System.out.println("搜索了" + counter + "次,left = " + left + ",right = " + right + ",mid = " + mid);++counter;if (nums[mid] == target) {return mid;} else if (nums[mid] > target) {right = mid - 1; // 注意} else if (nums[mid] < target) {left = mid + 1; // 注意}}return -1;}

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

        答:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。这二者可能出现在不同功能的二分搜索中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。这个算法中使用的是前者 [left, right],两端都闭的区间。这个区间其实就是每次进行搜索的区间。while (left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],带个具体的数字进去,即 [2, 2],这个时候区间非空,还有一个数2,但此时 while 循环终止了。也就是说,区间 [2, 2] 被漏掉了,索引2没有被搜索,如果这时候直接返回-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,3,3,3,4],target = 3,此算法返回的是正中间的索引2。但是如果我想得到 target 的左侧边界,即索引1,或者想得到 target 的右侧边界,即索引3,则算法是无法处理的。这样的需求很常见,但有人也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分搜索对数级的复杂度了。

寻找左边界的二分搜索

        举个该场景的例子:给定一个排序的整数数组 nums 和一个整数目标值 target ,请在数组中找到 target ,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

        完整代码:

    public int leftBound(int[] nums, int target) {int left = 0, right = nums.length - 1;int counter = 1;// 搜索区间为 [left, right]while (left <= right) { int mid = left + (right - left) / 2;System.out.println("搜索了" + counter + "次,left = " + left + ",right = " + right + ",mid = " + mid);++counter;if (nums[mid] < target) {left = mid + 1; // 搜索区间为 [mid + 1, right]} else if (nums[mid] > target) {right = mid - 1; // 搜索区间为 [left, mid - 1] } else if (nums[mid] == target) {right = mid - 1; // 收缩右侧边界}}// 检查出界情况/*if (left >= nums.length || nums[left] != target) {return -1;}*/return left;}

寻找右侧边界的二分搜索

    public int rightBound(int[] nums, int target) {int left = 0, right = nums.length - 1;int counter = 1;// 搜索区间为 [left, right]while (left <= right) {int mid = left + (right - left) / 2;System.out.println("搜索了" + counter + "次,left = " + left + ",right = " + right + ",mid = " + mid);++counter;if (nums[mid] < target) {left = mid + 1; // 搜索区间为 [mid + 1, right]} else if (nums[mid] > target) {right = mid - 1; // 搜索区间为 [left, mid - 1]} else if (nums[mid] == target) {left = mid + 1; // 收缩左侧边界}}// 检查出界情况/*if (right < 0 || nums[right] != target) {return -1;}*/return right;}


http://www.mrgr.cn/news/31414.html

相关文章:

  • 【高分系列卫星简介——高分一号(GF-1)】
  • A. Make All Equal
  • MATLAB绘图基础8:双变量图形绘制
  • ELF文件结构
  • LeetCode337. 打家劫舍III
  • 【千帆AppBuilder】零代码+组件+代码节点方式实现AI应用《法定退休年龄计算器》
  • ArrayList和Array有什么区别?
  • 算法课习题汇总(2)
  • Data Lakehouse如何使用
  • BUUCTF-MISC-隐藏的钥匙
  • 三 auto占位符
  • Vue3中el-table组件实现分页,多选以及回显
  • 【Redis入门到精通三】Redis核心数据类型(List,Set)详解
  • 【Linux】进程概念
  • Zookeeper安装使用教程
  • JAVA8新特性——Optional
  • uboot:源码分析-启动第一阶段-start.S解析
  • IPD流程体系:IPD在硬件产品开发中的应用
  • NCNN 学习(2)-Mat
  • 嵌入式linux系统中rk3588芯片引脚基本操作