面试经典------二分搜索问题

二分搜索的优缺点

二分搜索(折半搜索)的Wikipedia定义:是一种在有序数组中查找某一特定元素的搜索算法。从定义可知,运用二分搜索的前提是数组必须是排好序的。另外,输入并不一定是数组,也有可能是给定一个区间的起始和终止的位置。
优点:搜索速度快,时间复杂度为O(logn),即对数时间,也称为对数查找。
缺点:要求待查找的数组或者区间是排好序的。对数组进行动态的删除和插入操作并完成查找,平均复杂度会变为O(n)。此时应当考虑采取自平衡的二叉查找树:

  • 在 O(nlogn) 的时间内用给定的数据构建出一棵二叉查找树;
  • 在 O(logn) 的时间里对目标数据进行搜索;
  • 在O(logn) 的时间里完成删除和插入的操作。
    因此,当输入的数组或者区间是排好序的,同时又不会经常变动,而要求从里面找出一个满足条件的元素的时候,二分搜索就是最好的选择。

二分搜索的实现方法

递归解法
优点:简洁;缺点:执行消耗大

def binarySearch(list,target):
    low = 0
    high = len(list)-1
    midindex = int(low+(high-low)/2)
    if target==list[midindex] :
        return list[midindex]
    elif target < list[midindex]:
        return binarySearch(list[:midindex-1],target)
    elif target > list[midindex]:
        return binarySearch(list[midindex+1:],target)
    
    return -1

非递归写法

def binarySearch2(list,target):
    low = 0
    high = len(list) - 1

    while low <= high :
        mid = int((low+high) /2 )
        if target == list[mid]:
            return mid
        elif target<list[mid]:
            high = mid-1
        elif target > list[mid]:
            low = mid+1
    return -1

二分搜索的面试题种类及解题思路

  1. 找确定的边界问题

边界分上边界和下边界,有时候也被成为右边界和左边界。确定的边界指边界的数值等于要找的目标数。
在这里插入图片描述
解题思路:关键点在于需要确定边界的条件。在本题中,如示例1所示,左边界的条件为,当前的值等于8,并且左边的数值比当前小(list[n-1]<list[n]),或者左边没有数即当前为第一个数。有边界条件同理

def searchRange(list,target):
    left_index= searchLeft(list,target)
    right_index = searchRight(list,target)
    if left_index == None or right_index==None:
        return [-1,1]
    return left_index,right_index
# 寻找下边界
def searchLeft(list,target):
    low = 0
    high = len(list)-1
    while low <= high:
        mid = int((low+high)/2)
        if target == list[mid] and (mid==0 or list[mid-1]<list[mid]):
            return mid
        elif target <= list[mid]:
            high = mid-1
        else:
            low = mid +1

def searchRight(list,target):
    low = 0
    high = len(list)-1
    while low <= high:
        mid = int((low+high)/2)
        if target == list[mid]:
            if list[mid+1]==None or list[mid]<list[mid+1]:
                return mid
        elif target <= list[mid]:
            high = mid - 1
        else:
            low = mid + 1
  1. 找迷糊的边界
    二分搜索可以用来查找一些模糊的边界。模糊的边界指,边界的值并不等于目标的值,而是大于或者小于目标的值。

例题:从数组 {-2, 0, 1, 4, 7, 9, 10} 中找到第一个大于 6 的数。

解题思路
边界条件:
1.该数一定要大于6
2.该数为第一个数,或者它之前的数比6小

def search(list,target):
    low= 0
    high = len(list)-1

    while low<=high:
        mid = (low+high)//2
        if target<list[mid] and (mid==0 or list[mid-1]<=target):#<=是为了解决6,7 这种情况
            return list[mid],mid
        if target<list[mid]:
            high=mid-1
        else:
            low = mid+1
  1. 旋转的排序数组
    在这里插入图片描述
    解题思路:关键于,判断一边是否为排序好的数组
def Search_best(list,target):
    low = 0
    high = len(list) - 1
    while low <= high:
        mid = int((low + high) / 2)
        if target == list[mid]:
            return mid
        # 右边是完整的排序数列
        if list[mid] < list[high]:
            if list[mid]< target<= list[high]:
                low = mid+1
            else:
                high = mid -1
        else:
            if list[low]<= target < list[mid]:
                high=mid -1
            else:
                low = mid +1

    return -1
  1. 不定长的边界
    例题:有一段不知道具体长度的日志文件,里面记录了每次登录的时间戳,已知日志是按顺序从头到尾记录的,没有记录日志的地方为空,要求当前日志的长度。
    解题思路
    可以把这个问题看成是不知道长度的数组,数组从头开始记录都是时间戳,到了某个位置就成为了空:{2019-01-14,2019-01-17,…,2019-08-04,….,null,null…}
//先通过getUpperBound函数不断地去试探在什么位置会出现空的日志。
intgetUpperBound(String[]logs,inthigh){
	if(logs[high]==null){
	return high;
	}
	return getUpperBound(logs,high*2);
}
//再使用二分搜索
int binarySearch(String[] logs, int low, int high) {
    if (low > high) {
        return -1;
    }
    int middle = low + (high - low) / 2;
 	if (logs[middle] == null && logs[middle - 1] != null) {
        return middle;
    }
     if (logs[middle] == null) {
        return binarySearch(logs, low, middle - 1);
      } else {
        return binarySearch(logs, middle + 1, high);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值