一.题目
把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组{ 3, 4, 5, 1, 2 }为{ 1, 2, 3, 4, 5 }的一个旋转,该数组的最小值为1。
二.思路
由于原始数组是递增排序的数组,我们可以发现旋转之后的数组仍然可以分为两个递增排序的子数组,而且前一个子数组中的任意一个元素都要大于后面子数组中的元素,而最小数字就位于两个子数组的交界处且位于交界线的右边。由于本题给出的数组在一定程度上是排序的,因此我们可以考虑利用二分查找的思路去寻找最小的元素。
我们利用两个指针,开始时,一个指向第一个元素,另一个指向末尾元素,中间指针指向第一个元素(防止出现特列-旋转数组就是原数组),然后我们进行搜索。如果该中间元素位于前面的子数组,那么它应该大于等于第一个指针指向的元素,此时数组中最小的元素应该位于该中间元素的后面,我们可以把第一个指针指向该中间元素,这样可以缩小寻找的范围,而且移动之后的第一个指针仍然位于前面的子数组;同理,如果该中间元素位于后面的子数组,那么它应该小于等于第二个指针指向的元素,此时数组中最小的元素应该位于该中间元素的前面,我们可以把第二个指针指向该中间元素,这样可以缩小寻找的范围,而且移动之后的第二个指针仍然位于后面的子数组。
这里还有一个需要考虑的问题就是,如果中间元素刚好与第一个元素,第二个元素的值相同,我们就无法区分中间元素位于哪个子数组,上面的判断也就出现了偏差,如数组{1,0,1,1,1}和数组{1,1,1,0,1}都可以看成递增排序数组{0,1,1,1,1}的旋转,颜色表示了子数组的构成。在这两种情况下,第一个指针和第二个指针指向的数字都是1,并且两个指针中间的数字也是1.,这3个数字相同,此时我们无法判断中间的数字是位于前面的子数组还是位于后面的子数组。因此,我们不得不采用顺序查找到办法。
三.代码
int Min(int* numbers,int length){ if(numbers == nullptr || length <= 0) throw new std::exception("Invalid parameters."); int index1 = 0; int index2 = length - 1; int indexMid = index1; while(numbers[index1] >= numbers[index2]){ if(index2 - index1 = 1){ indexMid = index2; break; } indexMid = index1 + (index2 - index1) / 2; if(numbers[index1] == numbers[index2] && numbers[index1] == numbers[indexMid] ) return MinInOrder(numbers,index1,index2); if(numbers[indexMid] >= numbers[index1]) index1 = indexMid; else if(numbers[indexMid] <= numbers[index2]) index2 = indexMid; } return numbers[indexMid]; } int MinInOrder(int* numbers,int index1,int index2){ int nMin = numbers[index1]; for(int i = index1 + 1;i <= index2;i++){ if(nMin < numbers[i]){ nMin = numbers[i]; } } return nMin; }
四.本题考点
- 考查应聘者对二分查找的理解。本题变换了二分查找的条件,输入的数组不是排序的,而是排序数组的一个旋转。这要求我们对二分查找的过程有很深入的理解。
- 考查应聘者的沟通能力和学习能力。本题面试官提出了一个新的概念:数组的旋转。我们要在很短的时间内去学习、理解这个新概念。在面试过程中,如果面试官提出新的概念,那么我们可以主动和面试官沟通,多问几个问题,把概念搞清楚。
- 考查应聘者思维的全面性。排序数组本身是数组旋转的一个特例。另外,我们要考虑到数组中有相同的数字的特例。如果不能很好的处理这些特例,就很难写出让面试官满意的完美代码。