算法学习笔记:15.二分查找 ——从原理到实战,涵盖 LeetCode 与考研 408 例题

在计算机科学的查找算法中,二分查找以其高效性占据着重要地位。它利用数据的有序性,通过不断缩小查找范围,将原本需要线性时间的查找过程优化为对数时间,成为处理大规模有序数据查找问题的首选算法。

二分查找的基本概念

二分查找(Binary Search),又称折半查找,是一种在有序数据集合中查找特定元素的高效算法。其核心原理是:通过不断将查找范围减半,快速定位目标元素

与线性查找逐个遍历元素不同,二分查找依赖数据的有序性(通常为升序或降序),每次都与查找范围的中间元素比较,根据比较结果缩小查找范围:

  • 若中间元素等于目标值,则查找成功。
  • 若中间元素大于目标值,则目标值只可能在左半部分(针对升序数据)。
  • 若中间元素小于目标值,则目标值只可能在右半部分(针对升序数据)。

例如,在有序数组[1, 3, 5, 7, 9, 11, 13]中查找目标值7,二分查找的过程如下:

  1. 初始范围为整个数组(索引0到6),中间元素为索引3的7,与目标值相等,查找成功。

再如查找目标值5:

  1. 初始范围0-6,中间元素7(索引3),7 > 5,缩小范围至0-2。
  1. 范围0-2,中间元素3(索引1),3 < 5,缩小范围至2-2。
  1. 范围2-2,中间元素5(索引2),与目标值相等,查找成功。

二分查找的算法思想

二分查找的核心思想是分治与缩小范围,基于有序数据的特性,通过每次与中间元素比较,将查找范围缩小一半,从而高效定位目标元素。其基本思路可概括为:

  1. 确定查找范围:初始化左边界left为0,右边界right为数据长度n-1(闭区间[left, right])。
  1. 计算中间位置:在当前范围中,计算中间索引mid = left + (right - left) / 2(避免left + right溢出)。
  1. 比较与缩小范围
    • 若nums[mid] == target:找到目标值,返回mid。
    • 若nums[mid] > target:目标值在左半部分,更新右边界right = mid - 1。
    • 若nums[mid] < target:目标值在右半部分,更新左边界left = mid + 1。
  1. 终止条件
    • 若left > right:范围无效,说明目标值不存在,返回-1。
    • 若找到目标值,提前返回索引。

二分查找的关键在于维护正确的查找范围避免边界错误。根据范围定义的不同(如左闭右开[left, right)),具体实现会略有差异,但核心思想一致。

二分查找的解题思路

使用二分查找解决实际问题时,需遵循以下步骤:

  1. 确认数据有序性:二分查找仅适用于有序数据(升序或降序,需统一处理),若数据无序,需先排序(但排序成本可能高于查找收益,需权衡)。
  1. 定义查找范围:明确left和right的初始值(如left=0,right=n-1),并始终保持范围的一致性(如闭区间或左闭右开)。
  1. 循环查找:在left <= right(闭区间)条件下循环,计算mid并比较,根据结果调整left或right。
  1. 处理边界与特殊情况
    • 数据为空时直接返回-1。
    • 目标值小于最小值或大于最大值时,可提前判断。
    • 存在重复元素时,需明确查找 “第一个出现”“最后一个出现” 还是 “任意一个”(需调整边界更新逻辑)。

LeetCode 例题及 Java 代码实现

例题 1:二分查找(LeetCode 704)

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

解题思路

标准二分查找问题,直接应用上述解题思路,使用闭区间[left, right]实现。

代码实现
public class BinarySearch {

    public int search(int[] nums, int target) {

        int left = 0;

        int right = nums.length - 1;

        while (left <= right) {

// 计算mid,避免left + right溢出

            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) {

        BinarySearch solution = new BinarySearch();

        int[] nums = {-1, 0, 3, 5, 9, 12};

        System.out.println(solution.search(nums, 9)); // 输出:4

        System.out.println(solution.search(nums, 2)); // 输出:-1

    }

}

例题 2:在排序数组中查找元素的第一个和最后一个位置(LeetCode 34)

给定一个按照升序排列的整数数组nums,和一个目标值target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,返回[-1, -1]。

解题思路

本题需查找重复元素的边界,可通过两次二分查找实现:

  1. 查找左边界:找到第一个>= target的位置,若该位置元素等于target,则为左边界;否则不存在。
  1. 查找右边界:找到最后一个<= target的位置,若该位置元素等于target,则为右边界。
代码实现
import java.util.Arrays;

public class SearchRange {

    public int[] searchRange(int[] nums, int target) {

        int leftBound = findLeftBound(nums, target);

// 若左边界不存在,直接返回[-1, -1]

        if (leftBound == -1) {

            return new int[]{-1, -1};

        }

        int rightBound = findRightBound(nums, target);

        return new int[]{leftBound, rightBound};

    }

// 查找左边界:第一个等于target的位置

    private int findLeftBound(int[] nums, int target) {

        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;

                right = mid - 1;

            } else if (nums[mid] > target) {

                right = mid - 1;

            } else {

                left = mid + 1;

            }

        }

        return result;

    }

// 查找右边界:最后一个等于target的位置

    private int findRightBound(int[] nums, int target) {

        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) {

        SearchRange solution = new SearchRange();

        int[] nums = {5, 7, 7, 8, 8, 10};

        System.out.println(Arrays.toString(solution.searchRange(nums, 8))); // 输出:[3, 4]

        System.out.println(Arrays.toString(solution.searchRange(nums, 6))); // 输出:[-1, -1]

    }

}

例题 3:搜索旋转排序数组(LeetCode 33)

整数数组nums按升序排列,数组中的值互不相同。在传递给函数之前,nums在预先未知的某个下标k(0 <= k < nums.length)上进行了旋转,使数组变为[nums[k], nums[k+1],..., nums[n-1], nums[0], nums[1],..., nums[k-1]]。例如,[0,1,2,4,5,6,7]旋转后可能变为[4,5,6,7,0,1,2]。给定旋转后的数组nums和一个整数target,如果nums中存在这个目标值,则返回它的索引,否则返回-1。

解题思路

旋转数组可分为两个有序子数组,仍可使用二分查找:

  1. 计算mid后,判断[left, mid]或[mid, right]是否为有序区间。
  1. 在有序区间内判断target是否存在,缩小查找范围。
代码实现
public class SearchRotatedSortedArray {

    public int search(int[] nums, int target) {

        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) {

        SearchRotatedSortedArray solution = new SearchRotatedSortedArray();

        int[] nums = {4, 5, 6, 7, 0, 1, 2};

        System.out.println(solution.search(nums, 0)); // 输出:4

        System.out.println(solution.search(nums, 3)); // 输出:-1

    }

}

二分查找与考研 408

在计算机考研 408 中,二分查找是数据结构与算法部分的核心考点,主要涉及以下内容:

1. 算法原理与实现

考研 408 重点考查二分查找的基本原理和不同场景下的实现,包括:

  • 标准二分查找(查找任意位置)。
  • 查找边界(第一个 / 最后一个等于目标值的位置)。
  • 变体场景(如旋转数组、山脉数组等)。

要求考生能手动模拟查找过程,并写出正确代码,尤其注意边界条件(如left与right的更新逻辑)。

2. 时间复杂度与空间复杂度

  • 时间复杂度:每次查找范围减半,最坏情况下需要log2(n) + 1次比较,时间复杂度为O(log n),远优于线性查找的O(n)。
  • 空间复杂度:迭代实现的二分查找空间复杂度为O(1)(仅需常数变量);递归实现为O(log n)(递归栈深度)。考研中多考查迭代实现。

3. 适用条件与局限性

  • 适用条件
    • 数据必须有序(升序或降序)。
    • 数据支持随机访问(如数组),链表等无法随机访问的结构不适用(需O(n)时间定位mid,失去优势)。
  • 局限性
    • 数据无序时需先排序(排序时间O(n log n),若仅查找一次,成本高于线性查找)。
    • 不适用于频繁插入 / 删除的场景(维护有序性成本高)。
    • 处理重复元素时,需额外逻辑确定边界。

4. 与其他查找算法的对比

考研常对比二分查找与线性查找、哈希查找的差异:

算法

数据要求

时间复杂度(平均)

空间复杂度

适用场景

二分查找

有序、随机访问

O(log n)

O(1)

大数据量、静态有序数组

线性查找

无要求

O(n)

O(1)

小数据量、无序数据、链表

哈希查找

无要求

O(1)(理想)

O(n)

频繁查找、内存充足

5. 递归实现与迭代实现

考研可能考查二分查找的递归实现,需注意递归终止条件和参数传递:

// 二分查找的递归实现

public int binarySearchRecursive(int[] nums, int left, int right, int target) {

    if (left > right) {

        return -1;

    }

    int mid = left + (right - left) / 2;

    if (nums[mid] == target) {

        return mid;

    } else if (nums[mid] > target) {

        return binarySearchRecursive(nums, left, mid - 1, target);

    } else {

        return binarySearchRecursive(nums, mid + 1, right, target);

    }

}

6. 错误分析与常见问题

考研中常考二分查找的边界错误,例如:

  • mid计算溢出(应使用left + (right - left)/2而非(left + right)/2)。
  • 循环条件错误(如left < right导致漏查)。
  • 边界更新错误(如right = mid而非right = mid - 1导致死循环)。

总结

二分查找凭借O(log n)的高效时间复杂度,成为处理有序数据查找的首选算法。本文通过详细讲解其思想、解题思路、LeetCode 实战及考研 408 考点,帮助你全面掌握这一算法。

学习时,需重点关注边界条件处理和不同场景的变体实现,通过手动模拟和代码练习加深理解。对于考研 408 考生,还需掌握时间 / 空间复杂度分析、适用条件及与其他算法的对比,确保在考试中准确应对各类相关题目。

二分查找的核心是 “缩小范围”,理解这一思想后,即使面对旋转数组等复杂场景,也能灵活调整逻辑,高效解决问题。

希望本文能够帮助读者更深入地理解二分查找,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呆呆企鹅仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值