-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Binary Search 总结帖 (更新完) #8
Comments
公瑾总结本总结参考/复制/修改了很多综合帖的内容,全部参考文档会赋予文章底部。 Binary Search | 基础
二分查找法的前置条件要拥有一个已经Sorted好的序列,这样在查找所要查找的元素时, 首先与序列中间的元素进行比较, 如果大于这个元素, 就在当前序列的后半部分继续查找, 如果小于这个元素, 就在当前序列的前半部分继续查找, 直到找到相同的元素, 或者所查找的序列范围为空为止.
Binary Search | 痛点
按照上面的伪代码写出了下列的Python代码
接下来说说二分查找的一些痛点。 痛点1: 溢出
在对两个Signed 32-bit数字进行相加时,有可能出现溢出,例如下面这种情况:
当left 与 right 之和超过了所在类型的表示范围的话, 这个和就会成为一个很随机的值, 那么 middle 就不会得到正确的值.
痛点2: 边界错误二分查找算法的边界, 一般来说分两种情况, 一种是左闭右开区间, 类似于 [left, right), 一种是左闭右闭区间, 类似于 [left, right]. 等等,区间是个什么鬼,左闭右开,左闭右闭又是个什么鬼?
需要注意的是, 循环体外的初始化条件, 与循环体内的迭代步骤, 都必须遵守一致的区间规则, 也就是说, 如果循环体初始化时, 是以左闭右开区间为边界的, 那么循环体内部的迭代也应该如此. 如果两者不一致, 会造成程序的错误. 比如下面就是错误的二分查找算法:
这个算法的错误在于, 在循环初始化的时候, 初始化 r=len(arr), 也就是采用的是左闭右开区间, 而当满足 target < arr[mid] 的条件是, target 如果存在的话应该在 [left, mid) 区间中, 但是这里却把 r 赋值为 mid - 1 了, 这样, 如果恰巧 mid-1 就是查找的元素, 那么就会找不到这个元素. 左闭右闭:包括End区间,end inclusive
左闭右开,不包括End区间, end exclusive
痛点2.5 : 死循环上面的情况还只是把边界的其中一个写错, 也就是右边的边界值写错, 如果两者同时都写错的话, 可能会造成死循环, 比如下面的这个程序:
这个程序采用的是左闭右闭的区间. 但是, Binary Search | 模板选择@gengwuli 提出了模板一致性的观点,我也十分赞同。公瑾的策略是95%的情况下都用以下模板:
极个别情况会采用以下的模板:
至于为什么不采用一个模板,是因为在大部分题的时候,运用第一种模板能够有更好的可读性 这里插一句话:用的惯的模板才是最适合你的,切记 Binary Search | 题型题目类型分为三类:
第一类题:有明确Target的题型这一类题,会要求你找一个Target值,一般找到就返回Target值的下标或者Boolean函数。基础题目举两个例子: Leetcode 374. Guess Number Higher or Lower
Leetcode 367. Valid Perfect Square
由于这一类题基本上就是套公式,直接考公式的几率很小。所以Medium以上的题目会对边界的选定模糊化,我们要做的是明确边界,然后再套公式。下面举例二分经典题: 33. Search in Rotated Sorted Array
第二类题:没有明确Target的题型
这一类的题比较多变,可能会要你找
在刷这类题前,我们先看看模板,在迭代退出的时候, Case 1: 首先这个程序肯定返回 Case 2: 根据我们While Loop的特性,有一个Edge的情况就是,L最大可以等于 这里可以看到,如果我们要返回array[l],系统是会报错的,因为我们的L已经越界了,这个局限性是这套模板的小短板,当然,只要大家记住这一点,以后就可以写出相对应的Edge Case处理。
返回
|
愚见: 首先是关于左闭右闭,合左闭右开。楼主对每一种情况分析的都很细致,但是实际操作起来往往需要统一口径,持续使用一种稳定的算法。这样实战的时候就不会被各种情况所干扰。个人认为左闭右闭更加直接,因为这样考虑左边界右边界都是同样的思路(l = m + 1, r = m - 1)。 综上所述,总结除了模板。 def search(arr:Array[Int], target: Int): Int = {
var (l, r) = (0, arr.length - 1)
while (l + 1 < r) {
val m = l + (r - l) / 2
if (arr(m) == target) {
return m
}
if (arr(m) < target) {
l = m
} else {
r = m
}
}
if (arr(l) == target) { return l }
if (arr(r) == target) { return r }
return -1
} 首先判断条件总是l + 1 < r, 不用担心mid = l 循环的情况。然后l = mid, r = mid, 不用担心+1-1的情况。 最后我们可能需要考虑,如果有重复元素怎么办,重复元素找左右边界。 |
太帅了,谢谢大佬呀~ |
总结特别到位 👍 |
Thanks! |
赞!提出一个修正:第二个模板还能针对,x不在arr里的情况(比如LC658)。如果x不在arr里,第一个模板就不能用。 |
LC658 如何用某章的模板解决哪?新手问题,见谅! |
总结的很好,很适合新手,下面是进阶帖: 写对二分查找不能靠模板,需要理解加练习 (附练习题,持续更新) |
非常漂亮的總結,想詢問
这个程序采用的是左闭右闭的区间. 但是, |
Binary Search
所有题目
Leetcode
Lintcode
题目的分类总结:
刷题先后顺序:
left
还是right
,不慌,送你边界处理神器left
和right
在每一层迭代之后的值了。The text was updated successfully, but these errors were encountered: