二分法实现及其无限可能

硬核剖析二分法:从基础到花式玩法的深度探索

一、开篇:二分法的暴力美学

在算法的江湖里,二分法绝对是「四两拨千斤」的典范。当别人还在O(n)的线性搜索泥潭里挣扎时,掌握二分法的开发者已经用O(log n)的时间复杂度完成了任务。这个将搜索空间每次对半切割的算法,就像拿着手术刀在有序数组里精准定位目标,每次操作都透露着简洁而优雅的暴力美学。

# 经典二分查找,左闭右闭区间实现
def binary_search(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

核心逻辑拆解

  1. 区间定义:左闭右闭区间[left, right],这种定义让left == right时依然有意义,所以while条件必须是left <= right
  2. 边界收缩:当nums[mid] > target时,直接将right设为mid - 1,相当于砍掉右半部分搜索空间。这种「剪枝」操作正是O(log n)复杂度的关键

二、进阶挑战:有序数组里的重复元素处理

现实中的数据往往没那么理想化,当有序数组里存在重复元素时,二分法该如何进化?

2.1 寻找首个匹配元素

# 找到首个匹配元素的二分法
def binary_search_first(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = (left + right) // 2
        if nums[mid] < target:
            left = mid + 1
        else:
            right = mid
    return left if nums[left] == target else -1

技巧点:当nums[mid] >= target时,right直接收缩到mid,这样能保证最后left指向的是首个匹配元素

2.2 获取所有匹配元素

def find_all(nums, target):
    first = binary_search_first(nums, target)
    if first == -1: return []
    last = binary_search_last(nums, target)
    return list(range(first, last + 1))

def binary_search_last(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = (left + right + 1) // 2  # 注意这里+1,避免死循环
        if nums[mid] > target:
            right = mid - 1
        else:
            left = mid
    return left if nums[left] == target else -1

进阶操作

  • 利用两个方向的二分查找,分别定位首个和最后一个匹配元素
  • mid = (left + right + 1) // 2的写法,确保在left == right - 1mid始终等于right,避免死循环

三、旋转数组:二分法的变形记

处理旋转有序数组时,二分法需要切换思维模式。以[4, 5, 6, 7, 0, 1, 2]为例,关键在于判断哪一半是有序的。

def search_rotated(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target: return mid
        
        # 判断左半部分是否有序
        if nums[left] <= nums[mid]:
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        # 右半部分有序
        else:
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    return -1

战术要点

  • 通过nums[left] <= nums[mid]判断左半部分是否有序
  • 一旦确定有序区间,就可以用常规二分法的逻辑缩小搜索范围

四、实战应用:查找元素的起始和结束位置

LeetCode经典题目:在O(log n)时间内找到目标值的起始和结束位置。

def searchRange(nums, target):
    def first_position():
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid
        return left if nums[left] == target else -1

    def last_position():
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right + 1) // 2
            if nums[mid] > target:
                right = mid - 1
            else:
                left = mid
        return left if nums[left] == target else -1

    start = first_position()
    if start == -1: return [-1, -1]
    return [start, last_position()]

解题思路

  1. 定义两个独立的二分查找函数,分别定位起始和结束位置
  2. 利用「左边界查找」和「右边界查找」的不同逻辑,精准锁定目标范围

五、终极挑战:存在重复值的旋转数组

当旋转数组里还有重复元素时,情况变得棘手。比如[1, 1, 1, 0, 1, 1]这种数组,无法直接通过nums[left]nums[mid]判断哪一半有序。

def search_duplicate_rotated(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            # 向左找到首个匹配元素
            while mid > 0 and nums[mid] == nums[mid - 1]:
                mid -= 1
            return mid
        
        # 无法判断哪部分有序时的处理
        if nums[left] == nums[mid] == nums[right]:
            left += 1
            right -= 1
        elif nums[mid] <= nums[right]:  # 右半部分有序
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
        else:  # 左半部分有序
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
    return -1

破局关键

  • nums[left] == nums[mid] == nums[right]时,直接收缩边界left += 1right -= 1
  • 找到匹配元素后,通过循环向左扩展,确保返回的是首个匹配位置

六、总结:二分法的无限可能

从基础实现到各种花式变体,二分法的核心始终围绕「区间切割」和「条件判断」。真正掌握二分法的开发者,不仅要背熟代码模板,更要理解每种变体背后的逻辑演进。

在实际开发中,无论是数据库索引查找,还是大规模数据排序优化,二分法的思想都能带来显著的性能提升。下次遇到有序数据相关的问题时,不妨试试用二分法的思维去拆解问题,也许能发现意想不到的高效解法。

建议

  1. 手写实现所有变体代码,体会边界条件处理的微妙差异
  2. 尝试用二分法解决更多实际问题,比如查找平方根、搜索二维矩阵等
  3. 深入研究三分法、多分法等扩展算法,理解分治思想的更多可能性

现在,是时候带着二分法的武器,去算法世界里披荆斩棘了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值