Java 二分查找(二分算法)详解 + 实战运用 + 核心坑点

一、核心概念 & 前提条件

1. 什么是二分查找

二分查找(折半查找):在有序区间内,每次取中间元素对比,排除一半区间,逐步缩小范围,快速定位目标值 。本质:分治思想,每次砍掉一半数据,查询效率远高于遍历。

2. 必备前提(必背,不满足不能用)

  1. 数组 / 集合必须有序(升序 / 降序)
  2. 支持随机访问(数组、ArrayList 适合;LinkedList 不适合,无下标随机访问)

3. 时间复杂度

  • 最优:\(O(1)\)(一次命中)
  • 最坏 / 平均:\(O(\log_2 n)\)对比:顺序遍历 \(O(n)\),数据量越大,二分优势越明显。

二、基础实现(两种写法:循环版 + 递归版)

生产环境优先循环版;递归版仅用于理解思想,不推荐线上使用。

写法 1:循环实现(标准升序二分,最常用)

场景:有序数组,查找目标值 target,返回下标;不存在返回 -1

java

运行

复制代码
public class BinarySearch {
    // 标准二分查找(升序数组)
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        // 循环条件:左指针 <= 右指针
        while (left <= right) {
            // 计算中间下标(防溢出写法)
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                // 找到目标,返回下标
                return mid;
            } else if (arr[mid] < target) {
                // 目标在右区间,左指针右移
                left = mid + 1;
            } else {
                // 目标在左区间,右指针左移
                right = mid - 1;
            }
        }
        // 遍历完未找到
        return -1;
    }

    public static void main(String[] args) {
        int[] nums = {1, 3, 5, 7, 9, 11, 13};
        System.out.println(binarySearch(nums, 7));   // 3
        System.out.println(binarySearch(nums, 4));  // -1
    }
}

写法 2:递归实现(理解用,不推荐生产)

java

运行

复制代码
public static int binarySearchRecursive(int[] arr, int left, int right, int target) {
    // 递归出口:区间不存在
    if (left > right) {
        return -1;
    }
    int mid = left + (right - left) / 2;
    if (arr[mid] == target) {
        return mid;
    } else if (arr[mid] < target) {
        return binarySearchRecursive(arr, mid + 1, right, target);
    } else {
        return binarySearchRecursive(arr, left, mid - 1, target);
    }
}

递归缺点 :数据量大、查找深度深时,会触发 StackOverflowError 栈溢出。


三、核心关键点(编码必记,面试必考)

1. 中间值 mid 两种写法 & 整数溢出问题

错误写法(经典坑)

java

运行

复制代码
int mid = (left + right) / 2;

问题:当 leftright 都是接近 Integer.MAX_VALUE 时,两数相加溢出,变成负数,数组下标越界。

正确写法(工程通用)

java

运行

复制代码
int mid = left + (right - left) / 2;

原理:先算差值再除 2,永远不会溢出。

2. 循环条件 left <= right 还是 left < right

  1. left <= right (标准查找:精准找目标值)区间是 left, right,左右指针都有效,区间内元素全部判断。
  2. left < right (查找边界、左右区间、第一个 / 最后一个目标值)常用于:找左边界、右边界、大于 / 小于目标的第一个元素

3. 指针移动规则(死循环重灾区)

以升序为例:

  • arr[mid] < target:目标在右侧 → left = mid + 1
  • arr[mid] > target:目标在左侧 → right = mid - 1

❌ 禁止写 left = mid / right = mid(大概率死循环)。

4. 数组顺序区分

上面代码基于升序 ;如果是降序数组,大小判断逻辑反转即可。


四、进阶场景:二分查找边界(笔试 / 算法高频)

实际业务常遇到:数组存在重复元素,需要找:

  1. 目标值第一次出现的下标(左边界)
  2. 目标值最后一次出现的下标(右边界)

示例数组:[1,2,2,2,3,4],找 target=2

1. 查找左边界(第一个 2)

java

运行

复制代码
// 返回第一个等于 target 的下标;无则返回-1
public static int findLeftBound(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] >= target) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    // 最后校验是否等于目标
    return arr[left] == target ? left : -1;
}
2. 查找右边界(最后一个 2)

java

运行

复制代码
public static int findRightBound(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    while (left < right) {
        // 向上取整,避免死循环
        int mid = left + (right - left + 1) / 2;
        if (arr[mid] <= target) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }
    return arr[left] == target ? left : -1;
}

关键点:找右边界时,mid 需要向上取整,否则死循环。


五、项目实战运用场景(工作中怎么用)

  1. 有序数据快速检索商品编号、用户 ID、字典编码等有序数组 / 列表查询,替代全量遍历,提升性能。
  2. 查找边界值
    • 分页区间定位
    • 分数区间划分(评级、等级判定)
    • 时间有序日志区间筛选
  3. 旋转有序数组查找 (算法题)部分有序的特殊数组(如 [4,5,6,1,2,3])二分变种。
  4. 平方根、开方计算 用二分逼近法计算 √x,替代数学公式。
  5. 海量有序数据分片大数据分片、分桶,快速定位数据所在分片。

六、二分算法 经典坑点 + 原因 + 解决方案(重点背诵)

坑 1:未保证数组有序,直接使用二分

现象

结果随机错误、找不到存在的元素。

原因

二分核心依赖有序,无序数组逻辑完全失效。

解决

使用前先排序;业务保证数据本身有序。


坑 2:(left + right) / 2 整数溢出

现象

下标变成负数,抛出 ArrayIndexOutOfBoundsException 数组越界。

原因

leftright 接近 Integer.MAX_VALUE,相加超出 int 范围。

解决

统一使用安全写法:int mid = left + (right - left) / 2


坑 3:指针更新错误,导致死循环(最高发)

错误示例

java

运行

复制代码
// 错误:left = mid 会死循环
if (arr[mid] < target) {
    left = mid;
}
原因

leftright 相邻时,mid 永远等于 left,区间不再收缩,循环永不退出。

解决

标准查找:left = mid + 1right = mid - 1;边界场景严格对应指针规则。


坑 4:循环条件 left <= rightleft < right 混用

现象

漏查元素、边界查找结果错误。

区分
  1. 精准查找单个元素 → left <= right
  2. 查找左右边界、区间划分 → left < right

坑 5:重复元素场景,直接用基础二分

现象

只能查到重复元素中任意一个,无法拿到第一个 / 最后一个。

解决

改用边界二分(左边界 / 右边界专用代码)。


坑 6:递归版二分,大数据量栈溢出

现象

StackOverflowError

解决

生产环境一律使用循环迭代版,递归仅用于学习演示。


坑 7:对 LinkedList 使用二分查找

现象

性能极差,退化为全量遍历。

原因

LinkedList 基于链表,不支持 O (1) 随机访问 ,获取 arr[mid] 需要遍历下标。

解决

二分只用于数组、ArrayList 这种支持随机访问的结构。


坑 8:忽略数组为空 / 空指针

现象

NullPointerException / 数组下标异常。

解决

方法开头做非空判断:

java

运行

复制代码
if (arr == null || arr.length == 0) {
    return -1;
}

七、二分算法优化 & 拓展

  1. 插值查找 二分是固定折半,插值根据目标值动态计算 mid,适合分布均匀的有序数组
  2. 斐波那契查找利用斐波那契数列分割区间,适合有序 + 大多在左侧的数据。
  3. 二分 + 哈希有序数组二分定位下标,结合哈希表处理重复数据。

日常开发标准二分足够,插值 / 斐波那契仅算法面试了解即可。


八、面试高频简答题(直接背诵)

  1. 二分查找前提是什么? 数据必须有序,且支持随机访问。

  2. 为什么 (left+right)/2 会溢出?怎么解决? 两数之和超出 int 最大值;改用 left + (right - left) / 2

  3. 二分查找时间复杂度?\(O(\log n)\),远优于顺序遍历。

  4. 二分查找死循环一般是什么原因? 指针更新错误(left=mid/right=mid)、循环条件使用不当。

  5. **数组有重复元素,如何找到目标第一个 / 最后一个位置?**使用左边界、右边界专用二分逻辑,调整指针和循环条件。


九、终极背诵口诀

二分查找先有序,随机访问是前提; mid 计算防溢出,左加差值除以二; 精准查找小于等于,边界查找小于号; 指针移动加减一,防止循环死到底; 重复元素找边界,两套逻辑要分清; 链表不用递归弃,空数组先判安全第一。

相关推荐
洛水水1 小时前
【力扣100题】84.字符串解码
算法·leetcode·职场和发展
仍然.1 小时前
SpringBoot快速上手
java·spring boot·后端
ch.ju1 小时前
Java程序设计(第3版)第四章——重载和覆盖的区别
java·开发语言
AI科技星1 小时前
第四卷:橡皮泥江湖(拓扑学)
c语言·开发语言·网络·量子计算·agi·拓扑学
浮尘笔记1 小时前
Go实现大文件异步流式采集引擎
开发语言·后端·golang
yugi9878381 小时前
基于C#实现数字识别率的OCR方案
开发语言·c#·ocr
仍然.1 小时前
Spring MVC(1)---介绍Spring MVC 和 请求数据
java·spring·mvc
星越华夏1 小时前
python中四种获取文件后缀名的方法
开发语言·python
DianSan_ERP1 小时前
架构师视角:电商大促高并发下的订单API限流与防漏单架构演进
java·运维·网络·安全·微服务·架构·自动化