剑指offer——6.旋转数组的最小数字

1.题目

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 (NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。)

2.思路分析

    我们可以注意到旋转之后的数组其实可以划分为两个排序的子数组,并且该数组的特点是前面的子数组中的元素总是大于或者等于后面子数组的元素。其中最小的元素正好是这两个数组的分界线。本题给出的数组在一定范围内是排序的,因此我们可以用二分查找法来实现O(logn)的查找。具体的算法步骤如下:

  • 以数组{3,4,5,1,2}为例,我们可以找到数组的中间元素,定义两个指针P1和P2,左指针P1指向数组的第一个元素,右指针P2指向最后一个元素。如果中间元素位于前面的递增子数组,那么它应该大于或者等于P1所指向的元素。此时最小元素应该位于该中间元素之后,然后把P1指向该中间元素,移动之后P1仍然处于前面的递增子数组中。
  • 同样,若中间元素位于后面的递增子数组,那么其应该小于P2所指向的元素。此时最小元素应该在该中间元素之前,然后我们把P2指向该中间元素。移动之后P2仍然位于后面的递增子数组中。
  • P1总是指向前面递增子数组的元素,P2总是指向后面递增子数组的元素,最终P1和P2会指向两个相邻的元素,这就是循环结束的条件。

    3.特殊案例

  • 如果把排序数组的0个元素搬到最后面,这仍然是旋转数组,我们的代码需要支持这种情况。如果发现数组中的第一个数字小于最后一个数字,表明该数组是排序的,就可以直接返回第一个数字了。
  • 下面这种情况,即第一个指针指向的数字、第二个指针指向的数字和中间的数字三者相等,我们无法判断中间的数字1属于前面的递增子数组还是后面的递增子数组。这样的话,我们只能进行顺序查找。

    4.代码实现

    C++:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    class Solution {
    public:
    int minNumberInRotateArray(vector<int> rotateArray) {
    int size = rotateArray.size(); //数组长度
    if(size == 0){
    return 0;
    }
    int left = 0; //左指针P1
    int right = size - 1; //右指针P2
    int mid = 0; //中间指针
    while(rotateArray[left] >= rotateArray[right]){ //确保旋转
    if(right - left == 1){ //左右指针相邻
    mid = right;
    break;
    }
    mid = left + (right - left) / 2; //计算中间指针位置
    //特殊情况:如果无法确定中间元素是属于前面还是后面的递增子数组,只能顺序查找
    if(rotateArray[left] == rotateArray[right] && rotateArray[mid] == rotateArray[left]){
    return MinInOrder(rotateArray, left, right);
    }
    //中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面
    if(rotateArray[mid] >= rotateArray[left]){
    left = mid;
    }
    //中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面
    else{
    right = mid;
    }
    }
    return rotateArray[mid];
    }
    private:
    //顺序寻找最小值
    int MinInOrder(vector<int> &num, int left, int right){
    int result = num[left];
    for(int i = left + 1; i < right; i++){
    if(num[i] < result){
    result = num[i];
    }
    }
    return result;
    }
    };

Python2.7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
if len(rotateArray) == 0:
return 0
left = 0
right = len(rotateArray) - 1
mid = 0
while rotateArray[left] >= rotateArray[right]:
if right - left == 1:
mid = right
break
mid = left + (right - left) // 2
if rotateArray[left] == rotateArray[mid] and rotateArray[mid] == rotateArray[right]:
return self.minInorder(rotateArray, left, right)
if rotateArray[mid] >= rotateArray[left]:
left = mid
else:
right = mid
return rotateArray[mid]

def minInorder(self, array, left, right):
result = array[left]
for i in range(left+1, right+1):
if array[i] < result:
result = array[i]
return result