一道微软面试题:“半”有序数组如何进行二分查找?

Posted Python那些事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道微软面试题:“半”有序数组如何进行二分查找?相关的知识,希望对你有一定的参考价值。


最近是毕业季,有很多同学忙于找工作,参加了面试。看了一道面试题,题目如下:


问题1:对于一个先升序后降序的数组,比如数组[3, 5, 7, 8, 4, 2, 1],如何查找某个元素?


这让我想起了我在微软面试遇到的一道题目。很类似,但却有区别:


问题2:一个分段有序的数组,两段分别有序,连接后也是有序的,比如 [4, 5, 7, 8, 1, 2, 3],如何查找某个元素?


区别是什么呢?那就是问题2更像是一个“半”有序数组,是环形有序,在缩小查找范围更容易,因而解决起来更容易。今天,就探讨下这两个问题的解法。


顺序查找


首先,最简单的,就是顺序查找。这对于任何有序的、无序的数组都适用,当然,这并不是面试官想要的算法,这种算法是无法获得offer的。我们可以简单实现下:

def sequence_search(list, item):
   '''
   :param list: 任何列表
   :param item: 要查找的元素
   :return: item在list中的索引,若不在list中返回None
   '''

   iter = 0
   while iter < len(list):
       if list[iter] == item:
           return iter
       else:
           iter = iter + 1
   return None


二分查找


这两个题目的特点都是分段有序。我们清楚,二分查找是实现有序数组查找的高效算法:

def binary_search(list, item):
   '''
   :param list: 有序列表
   :param item: 要查找的元素
   :return: item在list中的索引,若不在list中返回None
   '''

   low = 0
   high = len(list) - 1
   while low <= high:
       midpoint = (low + high) // 2
       if list[midpoint] == item:
           return midpoint
       elif list[midpoint] < item:
           low = midpoint + 1
       elif list[midpoint] > item:
           high = midpoint - 1
   return None

那么,先从问题2开始,咱们可以基于二分查找算法,需要考虑一个问题,即若list[midpoint] != item, 如何缩小查找范围呢?这可以分为两种情形讨论:


情形1: 二分查找中,对于数组[4, 5, 7, 8, 9, 1, 2, 3],list[low] < list[midpoint] ,说明low至midpoint这一段是升序的,在这种情况下,若 item < list[low]  且 item  > list[midpoint],那么说明要查找的元素并非在此段,此种情况下,需要在 midpoint+1到high这一段来查找;其余情况,说明 item 就在此段,那么需要在 low至midpoint-1这一段查找即可。


情形2: 二分查找中,对于数组[ 7, 8, 9, 1, 2, 3, 4, 5],list[low] > list[midpoint] ,说明midpoint至high这一段是升序的,在这种情况下,若 item < list[low]  且 item  > list[midpoint],那么说明要查找的元素就在此段,此种情况下,需要在 midpoint+1到high这一段来查找;其余情况,说明 item 不在此段,那么需要在 low至midpoint-1这一段查找即可。


Python代码实现如下:

def binary_search_for_halfsequence(list, item):
   '''
   :param list: 两段合并有序列表
   :param item: 要查找的元素
   :return: item在list中的索引,若不在list中返回None
   '''

   low = 0
   high = len(list) - 1
   while low <= high:
       midpoint = (low + high) // 2
       if list[midpoint] == item:
           return midpoint
       elif (list[low] <= list[midpoint] and (item < list[low] or item > list[midpoint])) or (
               list[low] >= list[midpoint] and (item < list[low] or item > list[midpoint])):
           low = midpoint + 1
       else:
           high = midpoint - 1
   return None

而对于问题1,最关键的是什么呢?


因为是先升序后降序,与问题2很不同,问题2两段可以构成一个有序数组,而问题1则无法实现。因为是两段有序的,所以最关键的一点,就是找到最大值的位置,这样既可获得有序数组,从而用二分查找完成。


那么,如何寻得这样一个数组的最大值呢?依旧是二分查找。这与问题2有点不同,问题2只需要考虑如何缩小查找范围即可,而问题1则还需要考虑,什么情形下是满足条件的最大值。


让我们分析看看。最大值的特点是什么?很明显,最大值若为index,则满足:

list[index] > list[index-1] 且 list[index] < list[index+1]


所以,就可以用二分查找来搜索最大值,考虑两种情形来缩小查找范围:


情形1:若list[midpoint-1] < list[midpoint] 并且 list[midpoint] < list[midpoint+1],说明midpoint此处是升序的,要找到最大值,只能在midpoint+1至high这一段来查找;


情形2:若list[midpoint-1] > list[midpoint] 并且 list[midpoint] > list[midpoint+1],说明midpoint此处是降序的,要找到最大值,只能在low至midpoint-1这一段来查找。


Python代码实现如下:

def max_search(list):
   '''
   :param list: 先升序后降序列表
   :param item: 要查找的元素
   :return: item在list中的索引,若不在list中返回None
   '''

   if len(list) == 1:
       return 0
   elif len(list) == 2:
       return 0 if list[0] > list[1] else 1
   else:
       low = 0
       high = len(list) - 1
       while low <= high:
           midpoint = (low + high) // 2
           if list[midpoint - 1] < list[midpoint] and list[midpoint] < list[midpoint + 1]:
               low = midpoint + 1
           elif list[midpoint - 1] > list[midpoint] and list[midpoint] > list[midpoint + 1]:
               high = midpoint - 1
           else:
               return midpoint

寻得最大值之后,在搜寻相关的数值就可以使用二分查找了。具体算法就留给大家来完成吧,大家有好的算法也可以在留言区分享哦。


小结


通过这两道题,我们可以分析在用二分查找时,最关键的两点:


  • 寻找满足条件的特点,比如相等或者是最大值;

  • 寻找缩小查找范围的条件,即要是下一步在low至midpoint-1范围内查找需要满足什么条件。

(完)


看完本文有收获?请转发分享给更多人

关注「Python那些事」,做全栈开发工程师

以上是关于一道微软面试题:“半”有序数组如何进行二分查找?的主要内容,如果未能解决你的问题,请参考以下文章

面试100题:二分查找

利用二分查找法来解一道列表排序题

经典面试题:请手写一个二分查找法

[经典面试题]二分查找问题汇总

二分查找 - 基础篇

二分查找专题总结 - 基础篇