zoukankan      html  css  js  c++  java
  • 【剑指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

    本文完。

  • 相关阅读:
    kafka 官方 Quickstart
    oracle11.2 安装
    Perl参考函数/教程
    Mysql参见SHOW命令总结
    MySQL的Innodb缓存相关优化
    Oracle、Mysql和SQL Server数据库连接的URL写法
    jredis 客户端 使用
    sql基本命令-存储过程
    NoSql系列目录ElasticSearch-mongodb
    loadrunner 运行场景-Controller及Load Generators宿主主机优化
  • 原文地址:https://www.cnblogs.com/xfxu/p/4579386.html
Copyright © 2011-2022 走看看