【超详细】二分查找(折半查找)核心知识点全解析(含多场景代码实现)

发布时间:2026/7/22 23:12:47
【超详细】二分查找(折半查找)核心知识点全解析(含多场景代码实现) 一、前言二分查找Binary Search是算法领域最经典、最高效的查找算法之一其时间复杂度为 O(logn)相比顺序查找的 O(n) 效率提升呈指数级。它是「分治思想」的典型应用也是面试中高频考察的算法考点。本文将从核心概念、底层原理、多场景代码实现、边界处理、面试题实战、生活场景应用等维度帮你彻底掌握二分查找的精髓解决新手最头疼的「边界越界」「找不到目标值」等问题。二、二分查找核心概念2.1 定义二分查找是一种针对有序的、连续存储的线性数据集如数组的高效查找算法。它通过不断将查找区间折半缩小查找范围直到找到目标值或确定目标值不存在。2.2 核心前提缺一不可数据有序必须是升序或降序排列本文以升序为例随机访问数据需存储在支持随机访问的结构中如数组链表不适用因为链表随机访问复杂度为 O(n)会抵消二分查找的优势无重复 / 可处理重复基础版处理无重复数据进阶版可处理重复数据的边界查找。2.3 核心思想分治确定查找区间的左边界left和右边界right计算区间的中间位置mid获取中间值nums[mid]比较nums[mid]与目标值target若nums[mid] target找到目标值返回 mid若nums[mid] target目标值在左半区间调整右边界为mid - 1若nums[mid] target目标值在右半区间调整左边界为mid 1重复步骤 2-3直到left right区间为空目标值不存在。三、二分查找执行流程可视化以升序数组[1,3,5,7,9,11,13]查找目标值7为例初始区间left0, right6 → mid(06)/23 → nums[3]7 target → 找到返回3 若查找目标值 5 第1轮left0, right6 → mid3 → nums[3]7 5 → right2 第2轮left0, right2 → mid1 → nums[1]3 5 → left2 第3轮left2, right2 → mid2 → nums[2]5 target → 返回2 若查找目标值 4不存在 第1轮left0, right6 → mid3 → 74 → right2 第2轮left0, right2 → mid1 → 34 → left2 第3轮left2, right2 → mid2 →54 → right1 此时 left2 right1 → 区间为空返回-1四、多场景代码实现Java 版4.1 基础版查找目标值是否存在无重复数据最常用的基础版本找到返回索引未找到返回 - 1。核心区间定义为「左闭右闭」[left, right]终止条件left right。/** * 二分查找基础版左闭右闭区间 [left, right] * param nums 升序无重复数组 * param target 目标值 * return 目标值索引未找到返回-1 */ public class BinarySearchBasic { public static int binarySearch(int[] nums, int target) { // 1. 处理边界数组为空或长度为0 if (nums null || nums.length 0) { return -1; } // 2. 初始化左右边界左闭右闭 int left 0; int right nums.length - 1; // 3. 循环查找区间不为空left right while (left right) { // 计算mid避免 (left right) 溢出等价于 (left right) / 2 int mid left (right - left) / 2; if (nums[mid] target) { // 找到目标值返回索引 return mid; } else if (nums[mid] target) { // 目标值在左半区间缩小右边界 right mid - 1; } else { // 目标值在右半区间缩小左边界 left 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, 5)); // 输出2 System.out.println(binarySearch(nums, 4)); // 输出-1 } }4.2 左边界版查找第一个等于目标值的索引含重复数据适用于数组有重复元素需找到「第一个出现」的目标值如[1,2,2,2,3]找第一个2返回 1。/** * 二分查找左边界版找第一个等于target的索引 * param nums 升序有重复数组 * param target 目标值 * return 第一个目标值索引未找到返回-1 */ public class BinarySearchLeftBound { public static int leftBoundBinarySearch(int[] nums, int target) { if (nums null || nums.length 0) { return -1; } int left 0; int right nums.length - 1; // 记录左边界初始为-1未找到 int result -1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { // 找到目标值不直接返回继续向左找更小的索引 result mid; right mid - 1; } else if (nums[mid] target) { right mid - 1; } else { left mid 1; } } return result; } // 测试 public static void main(String[] args) { int[] nums {1,2,2,2,3,4,5}; System.out.println(leftBoundBinarySearch(nums, 2)); // 输出1 System.out.println(leftBoundBinarySearch(nums, 6)); // 输出-1 } }4.3 右边界版查找最后一个等于目标值的索引含重复数据适用于数组有重复元素需找到「最后一个出现」的目标值如[1,2,2,2,3]找最后一个2返回 3。/** * 二分查找右边界版找最后一个等于target的索引 * param nums 升序有重复数组 * param target 目标值 * return 最后一个目标值索引未找到返回-1 */ public class BinarySearchRightBound { public static int rightBoundBinarySearch(int[] nums, int target) { if (nums null || nums.length 0) { return -1; } int left 0; int right nums.length - 1; int result -1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { // 找到目标值不直接返回继续向右找更大的索引 result mid; left mid 1; } else if (nums[mid] target) { right mid - 1; } else { left mid 1; } } return result; } // 测试 public static void main(String[] args) { int[] nums {1,2,2,2,3,4,5}; System.out.println(rightBoundBinarySearch(nums, 2)); // 输出3 System.out.println(rightBoundBinarySearch(nums, 6)); // 输出-1 } }4.4 变种版查找大于 / 小于目标值的第一个元素4.4.1 查找大于 target 的第一个元素/** * 查找大于target的第一个元素索引 * param nums 升序数组 * param target 目标值 * return 第一个大于target的元素索引无则返回数组长度 */ public class BinarySearchGreater { public static int findFirstGreater(int[] nums, int target) { if (nums null || nums.length 0) { return 0; } int left 0; int right nums.length - 1; // 初始值设为数组长度表示无大于target的元素 int result nums.length; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { // 找到更大的元素记录索引并向左找更小的索引 result mid; right mid - 1; } else { // 元素小于等于target向右找 left mid 1; } } return result; } // 测试 public static void main(String[] args) { int[] nums {1,3,5,7,9,11}; System.out.println(findFirstGreater(nums, 6)); // 输出3nums[3]76 System.out.println(findFirstGreater(nums, 11)); // 输出6无大于11的元素 } }五、二分查找的优缺点 适用场景维度优点缺点时间复杂度O(logn)效率极高n100 万时仅需约 20 次查找仅适用于有序数据集无序需先排序排序成本O(nlogn)空间复杂度迭代版为O(1)仅用几个变量递归版为O(logn)栈空间不适合频繁插入 / 删除的场景插入删除会破坏有序性重新排序成本高数据结构仅适用于数组支持随机访问链表不适用随机访问O(n)抵消二分优势适用场景静态数据集无频繁增删的高效查找有序数组的精准查找、边界查找数值范围查找如 x 的平方根、猜数字大小算法题中的优化手段如将暴力枚举的 O(n) 优化为 O(logn)。六、经典面试题实战附思路 代码6.1 题目 1x 的平方根LeetCode 69题目描述计算并返回 x 的平方根结果只保留整数部分如 x8返回 2。思路二分查找范围[0, x]找最大的 mid 满足mid*mid x。public class SqrtX { public static int mySqrt(int x) { if (x 0 || x 1) { return x; } int left 1; int right x; int result 0; while (left right) { int mid left (right - left) / 2; // 避免 mid*mid 溢出改用除法 if (mid x / mid) { result mid; left mid 1; } else { right mid - 1; } } return result; } public static void main(String[] args) { System.out.println(mySqrt(8)); // 输出2 System.out.println(mySqrt(16)); // 输出4 } }6.2 题目 2搜索旋转排序数组LeetCode 33题目描述升序数组在某点旋转如 [0,1,2,4,5,6,7] → [4,5,6,7,0,1,2]查找目标值要求时间复杂度 O(logn)。public class SearchRotatedArray { public static int search(int[] nums, int target) { if (nums null || nums.length 0) { return -1; } int left 0; int right nums.length - 1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { return mid; } // 左半部分有序 if (nums[left] nums[mid]) { // 目标值在左半有序区间 if (nums[left] target target nums[mid]) { right mid - 1; } else { left mid 1; } } else { // 右半部分有序 if (nums[mid] target target nums[right]) { left mid 1; } else { right mid - 1; } } } return -1; } public static void main(String[] args) { int[] nums {4,5,6,7,0,1,2}; System.out.println(search(nums, 0)); // 输出4 System.out.println(search(nums, 3)); // 输出-1 } }7.1 场景 1猜数字游戏1-1007.1.1 场景描述你心里想一个 1-100 之间的整数程序通过二分查找的方式「猜数字」每次你告诉程序 “猜大了”“猜小了” 或 “猜对了”程序最终快速找到目标数字相比顺序猜 1、2、3… 效率提升数十倍。7.1.2 解决思路初始查找区间left1right100每次猜区间中点mid根据用户反馈调整边界反馈 “大了”目标在左半区间right mid - 1反馈 “小了”目标在右半区间left mid 1反馈 “对了”结束查找输出结果。7.1.3 Java 代码实现import java.util.Scanner; /** * 生活场景1二分查找实现1-100猜数字游戏 */ public class GuessNumberGame { public static void main(String[] args) { Scanner scanner new Scanner(System.in); System.out.println( 猜数字游戏 ); System.out.println(请心里想一个1-100之间的整数我将用二分法猜出来); System.out.println(每次我猜完后请输入反馈); System.out.println( 1 → 我猜大了 2 → 我猜小了 3 → 猜对了); int left 1; int right 100; int guessCount 0; // 记录猜测次数 while (left right) { guessCount; int mid left (right - left) / 2; System.out.println(\n我猜数字是 mid); System.out.print(请输入你的反馈1/2/3); int feedback scanner.nextInt(); if (feedback 3) { System.out.println(太棒了我用了 guessCount 次猜对了目标数字是 mid); break; } else if (feedback 1) { // 猜大了缩小右边界 right mid - 1; System.out.println(哦猜大了我继续缩小范围...); } else if (feedback 2) { // 猜小了缩小左边界 left mid 1; System.out.println(哦猜小了我继续缩小范围...); } else { System.out.println(输入无效请输入1、2或3); guessCount--; // 无效输入不计入次数 } } // 理论上1-100最多7次就能猜对2^7128100 if (left right) { System.out.println(你是不是谎报反馈了我找不到这个数字); } scanner.close(); } }7.2 场景 2图书馆按页码找书籍位置7.2.1 场景描述图书馆的书架上按「书籍页码范围」有序摆放书籍比如书 1 对应页码 1-50、书 2 对应 51-100、书 3 对应 101-150…现在要找包含目标页码如 88 页的书在书架上的位置用二分查找快速定位。7.2.2 解决思路定义Book类包含「书籍编号、起始页码、结束页码」将书籍数组按「起始页码」升序排列满足二分查找的有序前提二分查找目标页码判断mid位置书籍的页码区间是否包含目标页码不包含则调整边界。7.2.3 Java 代码实现/** * 书籍实体类包含编号、起始页码、结束页码 */ class Book { private int bookId; // 书籍编号对应书架位置 private int startPage; // 起始页码 private int endPage; // 结束页码 public Book(int bookId, int startPage, int endPage) { this.bookId bookId; this.startPage startPage; this.endPage endPage; } // getter方法 public int getBookId() { return bookId; } public int getStartPage() { return startPage; } public int getEndPage() { return endPage; } } /** * 生活场景2图书馆按页码找书籍位置二分查找实现 */ public class FindBookByPage { /** * 查找包含目标页码的书籍编号 * param books 有序的书籍数组按startPage升序 * param targetPage 目标页码 * return 书籍编号未找到返回-1 */ public static int findBook(Book[] books, int targetPage) { if (books null || books.length 0) { return -1; } int left 0; int right books.length - 1; while (left right) { int mid left (right - left) / 2; Book midBook books[mid]; if (targetPage midBook.getStartPage() targetPage midBook.getEndPage()) { // 目标页码在当前书籍的区间内找到 return midBook.getBookId(); } else if (targetPage midBook.getStartPage()) { // 目标页码更小找左半区间 right mid - 1; } else { // 目标页码更大找右半区间 left mid 1; } } // 未找到包含目标页码的书籍 return -1; } public static void main(String[] args) { // 模拟图书馆书架5本书按起始页码升序排列 Book[] books { new Book(1, 1, 50), new Book(2, 51, 100), new Book(3, 101, 150), new Book(4, 151, 200), new Book(5, 201, 250) }; // 测试1找88页的书应返回2 int targetPage1 88; int bookId1 findBook(books, targetPage1); System.out.println(页码 targetPage1 对应的书籍编号 bookId1); // 输出2 // 测试2找155页的书应返回4 int targetPage2 155; int bookId2 findBook(books, targetPage2); System.out.println(页码 targetPage2 对应的书籍编号 bookId2); // 输出4 // 测试3找300页的书无返回-1 int targetPage3 300; int bookId3 findBook(books, targetPage3); System.out.println(页码 targetPage3 对应的书籍编号 bookId3); // 输出-1 } }7.3 场景 3快递站按编号找包裹位置7.3.1 场景描述快递站的货架上按「包裹编号」升序摆放包裹比如货架 11001、货架 21002、货架 31003… 货架 1001100现在要找目标编号的包裹在哪个货架用二分查找替代逐个查找提升效率。7.3.2 解决思路包裹编号数组是升序的满足二分查找前提二分查找目标编号找到后返回「索引 1」对应货架号未找到则返回 - 1表示无该包裹。7.3.3 Java 代码实现/** * 生活场景3快递站按编号找包裹的货架位置二分查找实现 */ public class FindPackageByNumber { /** * 查找目标包裹编号对应的货架位置 * param packageNums 升序的包裹编号数组索引货架号-1 * param targetNum 目标包裹编号 * return 货架号索引1未找到返回-1 */ public static int findPackageShelf(int[] packageNums, int targetNum) { if (packageNums null || packageNums.length 0) { return -1; } int left 0; int right packageNums.length - 1; while (left right) { int mid left (right - left) / 2; if (packageNums[mid] targetNum) { // 找到返回货架号索引1 return mid 1; } else if (packageNums[mid] targetNum) { // 包裹编号更大找左半区间 right mid - 1; } else { // 包裹编号更小找右半区间 left mid 1; } } // 未找到目标包裹 return -1; } public static void main(String[] args) { // 模拟快递站包裹编号1001-1100对应货架1-100 int[] packageNums new int[100]; for (int i 0; i 100; i) { packageNums[i] 1001 i; } // 测试1找编号1050的包裹应返回50 int target1 1050; int shelf1 findPackageShelf(packageNums, target1); System.out.println(包裹 target1 在货架 shelf1); // 输出50 // 测试2找编号1088的包裹应返回88 int target2 1088; int shelf2 findPackageShelf(packageNums, target2); System.out.println(包裹 target2 在货架 shelf2); // 输出88 // 测试3找编号1200的包裹无返回-1 int target3 1200; int shelf3 findPackageShelf(packageNums, target3); System.out.println(包裹 target3 在货架 shelf3); // 输出-1 } }八、总结核心要点回顾核心前提二分查找仅适用于有序、支持随机访问的数据集优先数组区间定义新手优先用「左闭右闭[left, right]」终止条件left rightmid 计算用left (right - left)/2避免溢出边界处理找第一个目标值找到后继续向左缩边界right mid - 1找最后一个目标值找到后继续向右缩边界left mid 1时间复杂度稳定的 O(logn)是二分查找的核心优势生活应用只要满足「数据有序、可折半缩小范围」就能用二分思想提升效率如猜数字、找书籍、找快递。学习建议先掌握基础版再攻克边界版和变种版重点理解「缩边界」的逻辑手动模拟执行流程如在纸上画区间变化解决「边界越界」问题尝试将生活中的查找场景转化为二分查找如找价格区间、找楼层加深对思想的理解刷 LeetCode 二分查找专题标签二分查找从简单到中等难度巩固实战能力。文末小结二分查找的核心不是「写代码」而是「分治思想」—— 将大问题拆分为小问题通过折半快速缩小范围。无论是编程算法还是日常生活只要抓住「有序」和「折半」两个核心就能用二分思想解决问题大幅提升效率。建议多写、多测、多模拟让二分查找成为你的「通用高效工具」。