美文网首页
二分搜索模板

二分搜索模板

作者: Robin92 | 来源:发表于2022-06-11 15:08 被阅读0次

源码,是最好的学习资料。

之前做二分查找没有一个方法准则,总是现抠代码实现的,对于边界情况总是把握不准,知道它是有一定准则的,但总是疏于整理。后来看到 go 的 sort 包中有实现二分查找算法,非常简洁,所以这里设计思路分析一波(自己再也不怕手撕二分了)。

go 源码 sort.Search

sort.Search(n int, f func(int) bool)

源码 sort.Search() 给出了使用准则:

  • 第一个参数 n 表示数列元素个数
  • 第二个参数为闭包函数 f func(i int) bool
  • sort.Search 方法查找的就是满足函数 f(i) == true 的第一个下标位置 k,所以对 f 函数有约定满足公式:
    f(i) == false, (i < k)
    f(i) == true, (i >= k)
    

sort.Search 的实现思路

为了更好的解释,这里不是 copy 的源码。而是一个普通的二分查找算法寻找 target 的下标或可插入的位置坐标。这可以是自己实现的一个模板。

所有的二分查找都可以转换成这个查找:查找符合条件的第一个元素的下标 k。这个条件 f 满足:当 i < k 时,f(i) == false;当 i >= k 时,f(i) == true

用二分的前提是数列一定是有序的,这不再多解释。

由上条件我们可以将这个数列简化为 [false, false, true, true, true],即找第一个为 true 的下标。

二分法的最基本的模板应该是这样:

for FOR_COND:
    mid = (left + right) / 2 // 取得中间点
    if cond(mid):
        right = NEXT_RIGHT // 动 right 
    else 
        left = NEXT_LEFT // 动 left

但细节就在模板里的三个占位语句上。如 FOR_COND 该选择 left < right 还是 left <= right?NEXT_RIGHT 或 NEXT_LEFT 应该是 mid±1 还是 mid。

这里我们将 left = left + 1 称为“left 向前游走一次”;right = right - 1 称为 “right 向后游走一次”。每次 mid = (left + right) / 2 后,mid 总要替换 left 或 right,即若替换 left 时可以为 left = mid 就是说“不游走”,若为 left = mid + 1 就是说“left 游走一次”。即可发现:

  • 让 left 游走,就可能让 left 从指向 false 变为指向 true
  • 让 right 游走,就可能让 right 从指向 true 变为指向 false

所以,NEXT_LEFT 的条件其实主要就是上述两种影响。

考虑

  • mid = (left + right) / 2 的计算中,left != right 时,mid 是有可能等于 left 而不可能等于 right
  • 而上述中排除的 left == right 条件正好可以做终止循环的条件。

所以,

  • 由第一个条件,我们最好让 left 游走,而 right 永远指向 true 的位置最好了。
  • 即 NEXT_RIGHT 处为 right = mid;NEXT_LEFT 处为 left = mid + 1
  • 这时,FOR_COND 就可以是 left < right
  • 由于 right 永远指向 true 的位置,为了避免全数列都是 false 的情况,我们让 right 最初为 n(n 为数列长度),即假设了 f(n) == true
  • 同时注意,数列全为 false 时,返回的下标是 n,是数列溢出的下标位置。

综上,我们可以写出一下模板:

func binarySearch:
    left, right = 0, n 
    for left < right:
        mid = left + (right - left) / 2 // 防溢出
        if cond(mid):
            left = mid + 1
        else 
            right = mid
// 最终得到 left 值为第一个满足 cond 的位置(注意可能为 n)

练习题

由上拿 力扣:35. 搜索插入位置 (找到 target 可插入的位置)为例,不难写出如下代码:

func searchInsert(nums []int, target int) int {
    cond := func(i int) bool { return nums[i] >= target }

    left := 0
    right := len(nums) // right may not idx
    for left < right { // stop when left == right
        mid := left + (right-left)/2 // mid could be left but won't be right, if left != right
        if cond(mid) {
            right = mid // right is always hit the cond
        } else {
            left = mid + 1 //
        }
    }
    return left
}

力扣:278. 第一个错误的版本 更像是进一步让自己设计出 sort.Search 的代码出的题:

// var isBadVersion func(i int) bool // 力扣的伪代码
func firstBadVersion(n int) int {
    left:= 0
    right:= n

    for left < right {
        mid:= left + (right - left) / 2
        if isBadVersion(mid) {
            right = mid
        }else {
            left = mid + 1
        }
    }
    return left
}

总结

可见,你可以通过变更上述的 cond 函数(必须满足前面都是 false,后面都是 true),来查找到合适的点(第一个使 cond(i) == true 的位置点 k)


香饽饽

go 包的 sort.Search 真实太香了。下面的题看到题后直接抠码三分钟就完成。

可见,熟悉内置的一些算法方法,可以让做题效率提升很多。力扣竞赛中常常有 20 分钟内做完四道题的人,可想而知。

func findPeakElement(nums []int) int {
    switch len(nums) {
    case 1:
        return 0
    case 2:
        if nums[0] > nums[1] {
            return 0
        }
        return 1
    }

    x := sort.Search(len(nums)-1, func(i int) bool {
        switch i {
        default:
            return nums[i]-nums[i+1] > 0
        }
    })
    return x
}
func findMin(nums []int) int {
    if len(nums) == 0 {
        return nums[0]
    }
    x := sort.Search(len(nums), func(i int) bool {
        return nums[i] < nums[0]
    })
    if x < len(nums) {
        return nums[x]
    }
    return nums[0]
}

相关文章

  • Algorithm进阶计划 -- 二分搜索

    二分搜索二分搜索模板二分搜索运用 1. 二分搜索模板 二分搜索(二分查找)也称折半查找(Binary Search...

  • 二分算法-LeetCode 69

    二分算法-LeetCode 69 二分算法 二分算法模板, 二分搜索即搜索一个数,如果存在,返回其索引,否则返回-...

  • 二分搜索模板

    源码,是最好的学习资料。 之前做二分查找没有一个方法准则,总是现抠代码实现的,对于边界情况总是把握不准,知道它是有...

  • 二分查找

    [TOC] 二分查找的基础模板 二分查找靠左的Index基础模板 二分查找靠右的Index基础模板 二分查找插入t...

  • 算法-二分搜索算法

    算法:二分搜索算法(折半查找算法)时间复杂度: 二分搜索算法概述 二分搜索算法伪代码 二分搜索算法实现 二分搜索算...

  • Elasticsearch系列---几个高级功能

    概要 本篇主要介绍一下搜索模板、映射模板、高亮搜索和地理位置的简单玩法。 标准搜索模板 搜索模板search te...

  • 二分查找模式

    二分查找通用的模板int mid = (left + right) / 2容易溢出 二分查找的通用模板 使用“左边...

  • 搜索算法

    顺序搜索 二分搜索

  • 二分搜索(Binary_Search)

    1. 二分搜索是什么? 二分搜索(英语:binary search),也叫折半搜索(英语:half-interva...

  • AVL 树

    一:什么是 AVL 树? 在我的上一篇文章《二分搜索树与二分查找法》中,详细介绍了二分搜索树这种数据结构。二分搜索...

网友评论

      本文标题:二分搜索模板

      本文链接:https://www.haomeiwen.com/subject/uniimrtx.html