题目:把一个数组,最开始若干个个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个人旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
在线测试平台:http://ac.jobdu.com/problem.php?pid=1386
这道题最直观的解法并不难,从头到尾遍历数组一次,就可以找到最小的元素。这种思路的时间复杂度为O(n)。但是这个思路没有利用输入的旋转素数的特性。我们可以注意到最小元素刚好是两个子数组的分界线。在排序数组中我们可以利用二分查找法实现O(logn)的查找。本题给出的数组在一定程度上是排序的,因此我们可以试着用二分查找法的思路来寻找这个最小元素。
任何在一定程度上排序的数组,都可以尝试利用二分法进行查找。
二分法的思想是:
- 一个指针p1指向数组的第一个元素,另一个指针p2指向数组的最后一个元素。
- 不断地取两个指针的中间元素,然后根据中间元素的值判断下一步指针的移动:
- 中间元素是要找的值,直接返回中间元素。(因为数组是有序的,所以可以判断出目标元素所处的范围)
- 要找的值不在p1和p2之间,报错返回。
- 要找的值在中间元素和p1之间,将p1移到到中间元素的位置。(因为数组是有序的,所以可以判断出目标元素所处的范围)
- 要找的值在中间元素和p2之间,将p2移动到中间元素的位置。
- 不能根据p1和p2与中间元素的值,判断出目标元素处是在p1与中间元素之间,还是中间元素与p2之间(即不能判断出缩小范围的方向),证明此种情况二分法不可用,报错返回。(当p1、p2、中间元素都相同时就会出现这种情况)
- 在上面的步骤中p1、p2逐渐接近,直至相邻。p1、p2相邻(即p2-p1 == 1)是二分搜索的结束标志,可以根据题目需求返回值或未找到。
二分法的要点:
- p1一定要始终在目标元素的前面,p2一定要始终在目标元素的后面。
- 要注意排序数组是否存在不能判断缩小范围的方向的情况。凡是排序数组是非严格递增的(即相邻项可能相等),都可能出现p1、p2、目标元素相等的情况,这样就无法判断出缩小范围的方向了。
由于我们这个问题中搜索的不是具体的某个值,而是要搜索数组的最小元素,所以需要不断缩小范围直到p1和p2相邻。
注意此问题中的两个二分法要点:
- 因为目标元素是第二个子数组的第一个元素,所以p1始终在第一子数组中,p2始终在第二个子数组中。
- 这是一个非严格递增的数组,存在无法判断缩小方向的情况。
用上面二分法的思想来解决这个问题:
- 用两个指针分别p1、p2指向数组的第一个元素和最后一个元素。用一个指针pm指向第一个元素。
- 在循环中缩小p1、p2间的范围,循环条件为p1指向的元素大于等于p2指向的元素(根据旋转数组的规则,第一个元素应该大于或等于最后一个元素)。(注意不能判断缩小方向的情况):
- 如果p2、p1相邻,说明此时p1指向第一子数组的最后一个元素,p2指向第二数组的第一个元素,直接返回p2指向的元素。
- 求中间元素指针,pm = (p1 + p2)/2;
- 如果中间元素和p1、p2都相等,则不能判断出缩小方向,只能用顺序查找法遍历。直接返回调用顺序查找法的结果。
- 如果中间元素大于等于p1,说明中间元素处在第一子数组中。因为目标元素为第二个子数组的第一个元素,所以将p1移到中间元素处。
- 如果中间元素小于等于p2,说明中间元素处在第二子数组中。将p2移到中间元素处。
- 返回指针pm指向的元素,即为最小元素。
注意可能存在旋转0个元素的情况,此时数组的第一个元素就是最小元素可以直接返回,这就是为什么把pm指向数组第一个元素的原因。