A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

第一课 binary search

1.经典二分查找

要解决的问题是:是在有序的序列中找到所需的数字。

中心思想是:将有序序列(假设为升序排列)中间的数字设为middle数值,左边第一个数字设为left,右边第一个数字设为right,将target与middle先做比较,若middle比target大,证明target应该出现在middle的左半段,则可以将middle右半段的序列舍弃,反之同理。

代码如下:


def binary_search(list1,target):    if list1 == None or len(list1) == 0:        return -1    left = 0    right = len(list1) - 1    while left <= right:        #这里要是没有=的话,在left=right的情况下#如果target正好等于list1[left] = list1[right]就会正好被漏判,返回-1.mid = int((left+right)/2)        if list1[mid] < target:            left = mid + 1        elif list1[mid] > target:            right = mid - 1        else:            return mid    return -1

此算法时间复杂度为:o(logn)(假设序列长度为n)

推导:序列长度为n,比较一次后,序列长度变为n/2,比较j次过后,序列长度变为n/(2^j),最终序列长度会变为1,所以令n/(2^j)=1,j = log2n,所以时间复杂度为o(logn)。

空间复杂度:o(1)

2.矩阵中的二分查找

问题:若给一个有序的矩阵如下所示

1 2 3 4

5 6 7 8

9 10 11 12

要在升序矩阵中查找一个目标数,利用二分查找应该怎么做?

中心思想:把矩阵转化为一个有序序列,然后再进行二分查找。

比如,此矩阵有4行4列,1这个数字的坐标为(0,0),1在数组中的index应该为0*4+0=0。

通用公式:矩阵m行n列,数字坐标为(i,j),index = i*m+j

反之,i = index/m, j = index%m

代码如下:


#矩阵二分查找def bs(matrix,target):    if matrix == None or len(matrix) == 0:        return -1    row = len(matrix)#行数col = len(matrix[0])#列数left = 0    right = row*col - 1    while left <= right:        mid = (left+right)//2        #转换indexmatrix的坐标i = mid//row        j = mid%col        if matrix[j] < target:            left = mid + 1        elif matrix[j] > target:            right = mid - 1        else:            return (i,j)    return -1

时间复杂度:o(log(row*col))

空间复杂度:o(1)


3.查找一个升序序列中离目标数最近的数字。

问题:一个升序序列中,或许并没有要找的目标数字,但是需要返回一个离目标数字最近的数字。

中心思想和问题1一致,只是要将left = mid+1和right= mid-1改为left=mid和right=mid,(因为是返回离目标数最近的数字,所以mid即使和target不一样也需要留下来判断是否离target近)。但是如果使用问题1的代码,会出现死循环问题。比如在[1,2,5]中寻找离3最近的数字时,到最后会出现left=right=1,那么target永远> mid[left+right//2],程序会一直死循环下去。宏观来说就是left=right=mid时,mid又不等于target,left和right就会因为left=mid或者right=mid以及mid= (left+right)//2的语句一直卡在left=right=mid这里。为了解决这个问题,需要把代码中while的条件放宽为left<right-1,让left永远不等于right就不会出现死循环问题。最后在代码最后加一个判断。

代码如下:



def closeted(list1,target):    if list1==None or len(list1)==0:        return -1    left = 0    right = len(list1)-1    while left < right - 1:        mid = (left+right)//2        if list1[mid] > target:            right = mid        elif list1[mid] < target:            left = mid        else:            return mid    return left if abs(list1[left]-target) < abs(list1[right]-target) else right

时间复杂度:o(logn)


4.如果升序序列中有重复的数值出现,比如[1,2,2,3,4],查找最早或者最后出现的2。

中心思想是在问题3的基础上加以变动,二分搜索到最后left=right-1之后,判断left和right是否等于target,查找最早出现就先判断left是否=target,查找最后出现就先判断right是否等于target。但是在判断的时候还是有点细微差别。具体见代码。

代码如下:


#寻找最后出现的targetdef last(list1,target):    if len(list1) == 0 or list1 == None:        return 1    left = 0    right = len(list1) - 1    while left < right - 1:        mid = (left + right)//2#第一条判断语句,在判断最后出现的target时,需要先确定可能重复的target在哪个区间,比如先判断序列的左半段有没有重复的target,以免漏判。        if list1[mid] > target:            right = mid        else:            left = mid    if list1[right] == target:        return right    if list1[left] == target:        return left    return -1

寻找最先出现的target时也需要先判断可能重复的target没有在右半段出现。

时间复杂度:o(logn)

5,寻找peak值

问题:找一个序列中,比旁边两个数都大的那个数(peak)。

中心思想:利用二分查找,设置一个mid。

if array[mid] > array[mid-1] and array[mid] > array[mid+1]:

return mid

if array[mid] < array[mid+1]:证明peak出现在mid的右半段,left=mid+1

if array[mid] < array[mid-1]:证明peak出现在mid的左半段,right = mid-1

代码如下:


def peakpick(nums):    if nums == None or len(nums) == 0:        return -1    left = 0    right = len(nums) - 1    while left < right - 1:        mid = (left+right)//2        if nums[mid] > nums[mid+1] and nums[mid]>nums[mid-1]:            return mid        if nums[mid] < nums[mid+1]:            left = mid+1        if nums[mid] < nums[mid-1]:            right = mid-1    return left if nums[left]>=nums[right] else right

这段代码只能查找出一个peak值,另外的就无法查找得出。

最后一句代码的原因是在程序最后已经查找到序列最边边了,只用查找到最大值即可。

6.在旋转序列中利用二分查找查找target。

旋转序列:类似于 3,4,5,0,1,2。这样的序列。





将345分为区间1,将012分为区间2。利用二分查找。

如果array[left] < array[mid]那么array[mid]落在区间1,如果array[left]<=target<array[mid],那么就在区间一进行搜索。反之在区间2进行搜索。

if array[mid]<array[left],那么mid落在区间2, 如果array[mid]<target<array[right],就在区间2中进行搜索,反之在区间1进行搜索。


def r(array,target):    if array == None or len(array)==0:        return -1    left = 0    right = len(array)-1    while left <= right:        mid = (left+right)//2        if array[mid] == target:            return mid        if array[mid] >= array[left]:#mid落在一区间if array[mid] > target and target > array[left]:                right = mid - 1            else:                left = mid + 1        else:            if array[mid] < target and target < array[right]:                left = mid + 1            else:                right = mid - 1      return -1


1 个回复

倒序浏览
奈斯,加油加油
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马