【剑指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 }

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

解法二:

 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\n", minVal);
37
38     return 0;
39 }

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

考虑数组 {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\n", 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\n", minVal2);
55
56     return 0;
57 }

编译与执行:

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

本文完。

时间: 2024-10-10 06:01:38

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

剑指offer面试题8——旋转数组的最小数字

题目1386:旋转数组的最小数字 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:6708 解决:1505 题目描述: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. 输入: 输入可能包含多个测试样例,对于每个测试案例, 输入的第一行为一个整数n(1<= n<=1000000):代表旋转数组的元素个数. 输入的第二行包括n

《剑指Offer》算法题——“旋转数组”的最小数字

题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个非递减序列的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. //这里说的非递减并不是乱序的,也是递增的,只不过递增的过程中可以有相同数字而已 #include <iostream> #include <vector> using namespace std; class Solution { public: int minNu

剑指offer(6)旋转数组的最小数字

题目描述: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素. 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. NOTE:给出的所有元素都大于0,若数组大小为0,请返回0. 解题代码: function minNumberInRotateArray(rotateArray) { // write code here if(rotateArray.length == 0){ retu

剑指offer 面试题33 把数组排成最小的数

题目链接: 剑指offer 题目链接: 把数组排成最小的数, 例如{3, 32, 321} 输出: 321323 解题思路: 我现在已经知道正确答案了, 就是没有办法去证明, 先去开会, 在开会的时候再去想. 代码: #include <iostream> #include <cstdio> #include <string> #include <vector> #include <cstring> #include <iterator&g

剑指offer 面试题33—把数组排成最小的数

题目: 输入一个正整数数组,把数组里面的所有数字连接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3, 32,  321},则打印这三个数字能排成的最小数字321323. 基本思想:O(nlogn) 两个数字m和n能拼接数字mn和nm.如果mn<nm,那么我们应该打印出mn,也就是m应该排在n的前面,我们定义此时m小于n:反之,如果nm<mn,我们定义n小于m.如果mn=nm,m等于n. 如对321和32,排序为32132<32321,所以321<32(这个小于

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

面试题 11. 旋转数组的最小数字 题目描述 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个非递减排序的数组的一个旋转, 输出旋转数组的最小元素. 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. NOTE:给出的所有元素都大于0,若数组大小为0,请返回0. 解答 复杂度:O(logN) + O(1) public class Solution { public int minNumberInRotateArray(int

剑指Offer 面试题36:数组中的逆序对及其变形(Leetcode 315. Count of Smaller Numbers After Self)题解

剑指Offer 面试题36:数组中的逆序对 题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 例如, 在数组{7,5,6,4}中,一共存在5个逆序对,分别是(7,6),(7,5),(7,4),(6,4)和(5,4),输出5. 提交网址: http://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188 或 htt

剑指offer面试题29:数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一般,请找出这个数字,例如输入一个长度为9的数组(1,2,3,2,2,2,5,4,2,).由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 个人第一眼想法是通过一个sort函数,再判断中间那数出现次数,只要出现多于n/2,就直接输出. 一般来说,最为直观的算法面试官都不会满意,那么有没有更优的算法呢? 这种算法是受快速排序算法的启发.在随机快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字

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

///////////////////////////////////////////////////////////////////////////////////////////// //12. 题目八:旋转数组的最小数字 int RotatedBinarySearchMinNum(int aiArray[], int iLen) { int iLeft = 0; int iMid = 0; int iRight = iLen - 1; while (aiArray[iLeft] >= ai

【剑指Offer学习】【面试题8 : 旋转数组的最小数字】

题目: 把一个数组最开始的若干个元素搬到数组的末尾, 我们称之数组的旋转.输入一个递增排序的数组的一个旋转, 输出旋转数组的最小元素.例如数组{3,4, 5, 1, 2 }为{ l1,2,3, 4,5}的一个旋转,该数组的最小值为1 实现代码如下: public class Test08 { /** * 把一个数组最开始的若干个元素搬到数组的末尾, 我们称之数组的旋转. * 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素. * 例如数组{3, 4, 5, 1, 2}为{l ,2, 3,