文章同步发表于,知乎专栏和CSDN
1、什么是二分查找
二分查找是一种非常有效的查找方式,我们日常生活中也经常用到。简单来说就是在有序的集合中查找目标值。注意这里有个前提的条件就是有序。 下面是二分查找中常见的一些术语
目标 Target —— 你要查找的值
索引 Index —— 你要查找的当前位置 左、右指示符 Left,Right —— 我们用来维持查找空间的指标
中间指示符 Mid —— 我们用来应用条件来确定我们应该向左查找还是向右查找的索引
from leetcode
2、复杂度分析
- 时间复杂度 由于数折半查找,每次的数据范围都会缩小一般,假设数据规模为n,执行过程就是
其中k是循环次数
可以得出
所以时间复杂度为
,这是平均时间复杂度,也是最坏情况下的时间复杂度,最好的情况下时间复杂度为
- 空间复杂度
由于在查找过程中只需要存储一个变量,所以空间复杂度为
3、习题讲解
- 经典模版
对于二分查找而言,有几个常见的坑,在有些时候会进入死循环的情况,常见的难点有,最后的终止条件是什么,中间过程中的index到底怎么变化,下面介绍一种常见的二分法的模版
class Solution:
"""
@param nums: An integer array sorted in ascending order
@param target: An integer
@return: An integer
"""
def findPosition(self, nums, target):
# write your code here
if nums == None or len(nums) < 1:
return -1
start = 0
end = len(nums) - 1
while start + 1 < end:
mid = start + (end - start)//2
if nums[mid] == target:
return mid
elif nums[mid] > target:
end = mid
else:
start = mid
if nums[start] == target:
return start
if nums[end] == target:
return end
return -1
这里已python代码为例,我们发现上面的模版有几个特点
循环终止条件 start +1 < end ,那这个条件是什么意思呢,跟我们平常常见的二分法的条件好像不太一样,这里的终止条件的意思是,相邻即退出,同时start和end都在有效的区间范围内,并不会超出数组的范围
这种写法的好处在哪,能避免大部分死循环的出现
指针的变化 start = mid 或者 end = mid 因为我们的循环终止条件是相邻即退出,所以我们在变化指针的时候,直接使用mid进行赋值
边界验证 由于是相邻即退出,我们这里并没有对最后的边界情况进行验证,所以最后需要对边界情况进行验证
- 第一次出现的位置
题目的具体信息可以查看上述链接,这里的要求是查找出第一次出现的位置
给定一个排序的整数数组(升序)和一个要查找的整数target, 用O(logn)的时间查找到target第一次出现的下标(从0开始),如果target不存在于数组中,返回-1。
这里我们看跟上一题的差距,这道题中的数据有可能是重复的,而且是第一次出现的位置,那么我们要寻找的是,target第一次出现的那个位置边界,我们看一下题解
class Solution:
"""
@param nums: The integer array.
@param target: Target to find.
@return: The first position of target. Position starts from 0.
"""
def binarySearch(self, nums, target):
# write your code here
if nums == None:
return -1
start = 0
end = len(nums) - 1
while start + 1 < end:
mid = start + (end - start)//2
if nums[mid] < target:
start = mid
else:
end = mid
if nums[start] == target:
return start
if nums[end] == target:
return end
return -1
我们到题解中,如果 nums[mid] < target 此时还没有到达target的位置,所以index往右边移动,而对于其他情况index一律往左边移动,这就对应题目中的我们要找的第一次出现的位置
- 建庙
这道题目与Lintcode上的木头加工题目一样
你是一名建造寺庙的建筑师。 寺庙的柱子是由木头制成。每根柱子必须是一节完整的木头而且不能是被连接得到的。 给出n段具有不同长度的木头。你的寺庙有m根高度严格相同的柱子。那么你寺庙最大高度是多少。 (m根柱子的高度)
首先,这里我们看对哪个数据进行二分,二分应用的条件之一就是有序,那么上述的条件中那个变量是有序的呢? 答案就是最终柱子的高度 柱子高度的范围是1,2,3... max(len),我们要在这个范围内进行二分查找,找到满足条件的长度
class Solution:
"""
@param m: m pillars of your temple.
@param woods: length of n different wood
@return: return the maximum height of the temple.
"""
def buildTemple(self, k, L):
# write your code here
if k <= 0 or L == None or len(L) == 0:
return 0
end = max(L)
start = 1
while start + 1 < end:
mid = start + (end - start)//2
if self.cut(L ,mid) >= k:
start = mid
else:
end = mid
if self.cut(L, end) >= k:
return end
elif self.cut(L, start) >= k:
return start
return 0
def cut(self, L , cut_len):
num = 0
for item in L:
num = num + item // cut_len
return num
最后的的时间复杂度为 O(nlogLen) ,其中Len为n段柱子中最大的长度
4、使用二分查找注意的点
- O(logn) 我们一定要对这个时间复杂度比较敏感,因为出现这个时间复杂度的时候,往往需要优先考虑二分查找
- 二分法的终极思想 在循环中通过判断,不断的缩小范围,直到最后可以进行判断,所以我们要挖掘题目中隐含的有序的变量,比如最后一题中柱子的长度
好了今天的内容就这些,下次我们说一说关于树的相关知识