题目:旋转数组的最小数字

题目描述:

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

旋转数组的特点:

  (1)旋转之后的数组实际上可以划分为两个排序的子数组,且前面的子数组的元素都大于或者等于后面子数组的元素。(有一个特例,后面说明!)

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

  (3)旋转数组在一定程度上是排序的,因此可以用二分查找法的思路来寻找这个最小的元素。

思路:

  (1)设置两个指针,初始状态第一个指针指向前面子数组的第一个元素,第二个指针指向后面子数组的最后一个元素;

  (2)找到两个指针的中间元素;

  (3)若其大于等于第一个指针指向的元素,则说明其在前面的子数组中,且显然最小元素在中间元素的右边,若其小于等于第二个指针指向的元素,则说明其在后面的子数组中,且显然最小元素在中间元素的左边。如此,便可以缩小搜索范围,最终第一个指针指向前面子数组的最后一个元素,而第二个指针指向后面子数组的第一个元素,它们处于相邻位置,而第二个指针指向的刚好是最小的元素

  以题目举例的数组{3,4,5,1,2}分析,先把第一个指针指向第0个元素,把第二个指针指向第4个元素(如图2.10(a)所示)。位于两个指针中间(在数组中的下标是2)的数字是5,它大于第一个指针指向的数字。因此中间数字5一定位于第一个递增子数组中,并且最小的数组一定位于它的后面。因此可以移动第一个指针让它指向数组的中间(如图2.10(b)所示)。

  此时位于这两个指针中间(在数组中的下标是3)的数字是1,它小于第二个指针指向的数字。因此这个中间数字1一定位于第二个递增子数组中,并且最小的数字一定位于它的前面或者它自己就是最小的数字。因此可以移动第二个指针指向两个指针中间的元素,即下标为3的元素(如图2.10(c)所示)。

  注:旋转数组中包含两个递增排序的子数组,有阴影背景的是第二个子数组。

   (a)把P1指向数组的第一个数字,P2指向数组的最后一个数字。由于P1和P2中间的数字5大于P1指向的数字,中间的数字在第一个子数组中。下一步把P1指向中间的数字。

   (b)P1和P2中间的数字1小于P2指向的数字,中间的数字在第二个子数组中。下一步把P2指向中间的数字。

     (c)P1和P2指向两个相邻的数字,则P2指向的是数组中的最小数字。

  此时两个指针的距离是1,表明第一个指针已经指向了第一个递增子数组的末尾,而第二个指针指向第二个递增子数组的开头。第二个子数组的第一个数字就是最小的数字,因此第二个指针指向的数字就是我们查找的结果。

  注意两点:

  (1)按旋转规则,第一个元素应该是大于或等于最后一个元素的;但也有特例:若把排序数组的前0个元素搬到最后面,及排序数组本身,仍是数组的一个旋转,此时数组中的第一个数字是最小的数字,应该直接返回。

  (2)当两个指针指向的数字及它们中间的数字三者相等时,无法判断中间数字位于前面的子数组还是后面的子数组,也就无法移动两个指针来缩小查找的范围,此时只能用顺序查找的方法。

  例如:数组{1,0,1,1,1}和数组{1,1,1,0,1}都可看成是递增数组{0,1,1,1,1}的旋转。第一种情况,中间数字位于后面的子数组,第二种情况,中间数字位于前面的子数组。

  

 

鉴于此,实现的代码如下:

 1 /*
 2 Time:2016-9-21 17:01:41
 3 Author:CodingMengmeng
 4 */
 5 #include <iostream>
 6 using namespace std;
 7 
 8 int Min(int* data, int length);
 9 int MinInOrder(int* data, int index1, int index2);
10 
11 int main()
12 {
13 //    int nArr1[5] = { 3, 4, 5, 1, 2 }; 
14     int nArr1[10] = { 5, 6, 6, 7, 7, 8, 9, 2, 3, 4 };
15     cout << "the minimum value in nArr1 is " << Min(nArr1,10)<< endl;
16     int nArr2[5] = { 1, 0, 1, 1, 1 };
17     cout << "the minimum value in nArr2 is " << Min(nArr2, 5) << endl;
18     int nArr3[5] = { 1, 1, 1, 0, 1 };
19     cout << "the minimum value in nArr3 is " << Min(nArr3, 5) << endl;
20     int nArr4[5] = { 1, 2, 3, 4, 5 };
21     cout << "the minimum value in nArr4 is " << Min(nArr4, 5) << endl;
22     int* nArr5 = NULL;
23     cout << "the minimum value in nArr5 is " << Min(nArr5, 5) << endl;
24 
25     system("pause");
26     return 0;
27 }
28 
29 int Min(int* data, int length)
30 {
31     if (data == NULL || length <= 0)
32         throw new exception("Invalid parameters!");
33     int index1 = 0;
34     int index2 = length - 1;
35     int indexMid = index1;
36     //若把排序数组的前0个元素搬到最后面,及排序数组本身,
37     //仍是数组的一个旋转,此时数组中的第一个数字是最小的数字,应该直接返回。
38     //显然这种情况不满足while循环,所以直接返回。
39     while (data[index1] >= data[index2])
40     {
41         /*
42         两个指针的距离是1,表明第一个指针已经指向了第一个递增子数组的末尾,
43         而第二个指针指向第二个递增子数组的开头。
44         第二个子数组的第一个数字就是最小的数字,
45         因此第二个指针指向的数字就是我们查找的结果。
46         */
47         if (index2-index1==1)
48         {
49             indexMid = index2;
50             break;
51         }
52         indexMid = (index1 + index2) / 2;
53         /*
54         当两个指针指向的数字及它们中间的数字三者相等时,
55         无法判断中间数字位于前面的子数组还是后面的子数组,
56         也就无法移动两个指针来缩小查找的范围,此时只能用顺序查找的方法。
57         */
58         if (data[index1] == data[index2] && data[indexMid] == data[index1])
59             return MinInOrder(data, index1, index2);
60         if (data[indexMid] >= data[index1])
61             index1 = indexMid;
62         else if (data[indexMid] <= data[index2])
63             index2 = indexMid;
64     }
65     return data[indexMid];
66 }
67 
68 int MinInOrder(int* data, int index1, int index2)
69 {
70     int MinResult = data[index1];
71     for (int i = index1 + 1; i <= index2; i++)
72     {
73         if (MinResult > data[i])
74             MinResult = data[i];
75     }
76     return MinResult;
77 }

  运行结果:

  

  nArr5由于出现异常,在运行结果中未显示!

原文地址:https://www.cnblogs.com/codingmengmeng/p/5893376.html