【剑指offer】面试题八:旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为 1.

解法一:

 这道题最直观的解法就是遍历一遍数组,这样我们就能找到最小的元素。这种思路的时间复杂度显然是 O(n)。

代码如下:

 1 // findMin.c
 2 #include "stdio.h"
 3 #include "stdlib.h"
 4 
 5 #define N 5
 6 
 7 int findMin(int *arr, int len)
 8 {
 9     int minVal = arr[0], i;
10 
11     for(i = 1; i < len; i++)
12     {
13         if(minVal > arr[i])
14             minVal = arr[i];
15     }
16     return minVal;
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     int arr[N] = {3,4,5,1,2};
22 
23     int minNum = findMin(arr, N);
24     printf("The min Num is: %3d",minNum);
25 
26     return 0;
27 }
View Code


但是这个思路没有利用输入的旋转数组的特性,那么有没有效率更好地办法呢?

解法二:

 1、旋转时候的数组实际上可以划分为两个排序的子数组,而前面的子数组的元素都大于或者等于后面子数组的元素。

 2、最小的元素刚好是这两个子数组的分界线。

 3、第一个元素值应该是大于或者等于最后一个元素的。

在已经有序的数组中我们可以用二分查找法实现 O(log n)的查找,而旋转数组在一定程度上是排序的,因此我们可以试着用二分查找法的思路来寻找这个最小的元素。

分析:

 1、我们用两个指针pHead,pTail分别指向数组的第一个元素和最后一个元素,根据二分查找的规则,我们可以找到指向数组中间的元素的指针pMid。

 2、如果中间元素*pMid位于前面的递增子数组,那么它应该大于或者等于第一个指针pHead指向的元素。此时数组中最小的元素应该位于*pMid的后面。这样我们可以把第一个指针pHead指向*pMid。这样就可以缩小寻找的范围。移动之后的pHead仍然位于前面的递增子数组中。

 3、如果中间元素*pMid位于后面的递增子数组,那么它应该小于或者等于最后一个指针pTail指向的元素。此时数组中最小的元素应该位于*pMid的前面。这样我们就可以把最后一个指针pTail指向*pMid。而移动之后的pTail仍然位于后面的递增子数组中。

 4、不管是移动 pHead 和 pTail,查找的范围都会缩小到原来的一半。接下来我们再用更新之后的两个指针,重复做新一轮的查询。

 按照上述思路,第一个指针总是指向前面的递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是说,第一个指针与第二个指针最终会指向两个相邻的元素。这就是循环结束的条件。

 可以以数组{3,4,5,1,2}为例,画图试着分析。

 基于这个思路,我们写出如下的代码:

 1 // findMin.cpp
 2 #include "stdio.h"
 3 #include "stdlib.h"
 4 #include <stdexcept>
 5 
 6 #define N 5
 7 
 8 int findMin(int *arr, int len)
 9 {
10     if(arr == NULL || len <= 0)
11         throw std::out_of_range("Invalid parameters");
12 
13     int pHead = 0;
14     int pTail = len - 1;
15 
16     while(pHead < pTail)
17     {
18         if(pTail - pHead == 1)
19             break;
20 
21         int mid = (pHead + pTail) / 2;
22         if(arr[mid] >= arr[pHead]) // 前半部分
23             pHead = mid;
24         else if(arr[mid] <= arr[pTail])
25             pTail = mid;
26     }
27 
28     return arr[pTail];
29 }
30 
31 int main(int argc, char const *argv[])
32 {
33     int arr[N] = {3, 4, 5, 1, 2};
34 
35     int minVal = findMin(arr, N);
36     printf("The min num is: %d
", minVal);
37 
38     return 0;
39 }
View Code

但是上面仍存在例外情况:

考虑数组 {1,0,1,1,1}、{1,1,1,0,1}

 pHead   mid    pTail

① { 1,  0,   1,  1,  1}

根据上面的解法,此时arr[pHead] = 1、arr[pTail] = 1、arr[mid] = 1;

由语句 if(arr[mid] >= arr[pHead])  pHead = mid;  可知pHead将移动到 mid 所在的位置,而最小元素 0 位于数组的前半部分,这样就脱离了更新后的 pHead(mid) 与 pTail 的查找范围,这样就找不到最小元素 0 了。

 ② { 1,  1,   1,  0,  1} 与 ① 类似;

因此,在arr[pHead] = arr[pTail] == arr[mid] 的情况下,我们只能遍历数组进行查找了。

完整的代码如下

 1 // findMin.cpp
 2 #include "stdio.h"
 3 #include "stdlib.h"
 4 #include <stdexcept>
 5 
 6 #define N 5
 7 
 8 int seqSearch(int *arr, int len) // 顺序查找
 9 {
10     int minVal = arr[0], i;
11 
12     for(i = 1; i < len; i++)
13     {
14         if(minVal > arr[i])
15             minVal = arr[i];
16     }
17     return minVal;
18 }
19 
20 int findMin(int *arr, int len)
21 {
22     if(arr == NULL || len <= 0)
23         throw std::out_of_range("Invalid parameters");
24 
25     int pHead = 0;
26     int pTail = len - 1;
27 
28     while(pHead < pTail)
29     {
30         if(pTail - pHead == 1)
31             break;
32 
33         int mid = (pHead + pTail) / 2;
34         if(arr[mid] == arr[pHead] && arr[mid] == arr[pTail]) // 三者相等,顺序查找
35             return seqSearch(arr, len);
36 
37         if(arr[mid] >= arr[pHead]) // 前半部分
38             pHead = mid;
39         else if(arr[mid] <= arr[pTail]) // 后半部分
40             pTail = mid;
41     }
42 
43     return arr[pTail];
44 }
45 
46 int main(int argc, char const *argv[])
47 {
48     int arr[N] = {3, 4, 5, 1, 2};
49     int minVal = findMin(arr, N);
50     printf("1、The arr's min num is: %d
", minVal);
51 
52     int arr2[N] = {1, 0, 1, 1, 1};
53     int minVal2 = findMin(arr2, N);
54     printf("2、The arr2's min Num is: %d
", minVal2);
55 
56     return 0;
57 }
View Code

编译与执行:

1 g++ -0 findMin findMin.cpp
2 ./findMin

本文完。

原文地址:https://www.cnblogs.com/xfxu/p/4579386.html