剑指Offer(java版):旋转数组的最小数字

题目: 把一个数组最开始的若干个元素搬到数组的末尾,咱们称之为旋转。 输入一个递增的排序的数组的一个旋转,输出旋转数组的最小元素。面试

 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小元素为1.数组

这道题最直观的解法并不难,从头至尾遍历一次,咱们就能找到最小的元素。这种思路的时间复杂度为O(n)。可是这个思路没有利用输入的旋转数组的特性,确定达不到面试官的要求。.net

咱们注意到旋转以后的数组实际上能够划分为两个排序的子数组,并且前 面的子数组的元素都是大于或者等于后面子数组的元素。咱们还注意到最小的元素恰好是这两个子数组的分界线。在排序的数组中咱们能够利用二分查找来实现 O(logn)的查找。本题给出的数组在必定程度上是排序的,所以咱们能够试着用二分查找的思路来寻找这个最小的元素。指针

之前面的例子为例,咱们先把第一个指针指向第0个元素,把第二个指针 指向第4个元素,如图所示。位于两个指针中间(在数组的下标是2)的数字是5,它大于第一个指针指向的数字。所以中间数字5必定位于第一个递增字数组中, 而且最小的数字必定位于它的后面。所以咱们能够移动第一个指针让它指向数组的中间。排序

此时位于这两个指针中间的数字为1,它小于第二个指针指向的数字。所以这个中间数字为1必定位于第二个递增子数组中,而且最小的数字必定位于它的前面或者它本身就是最小的数字。所以咱们能够移动第二个指针指向两个指针中间的元素即下标为3的元素。索引

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

上述方法是否就必定够完美了呢?面试官会告诉你其实否则。他将提示咱们再仔细分析小标leftIndex和rightIndex分别和途中P1和 P2相对应)的两个数相同的状况。在前面,当着两个数相同,而且它们中间的数相同的也相同时,咱们把IndexMid赋给了leftIndex,也就是认 为此时最小的数字位于中间数字的后面。是否是必定同样?class

咱们再来看一个例子。数组{1,0,1,1,1}和数组{1,1,1,0,1}均可以堪称递增排序数组{0,1,1,1,1}的旋转,图2分别画出它们由最小数字分隔开的两个子数组。List

这两种状况中,第一个指针和第二个指针指向的数字都是1,而且两个指 针中间的数字也是1,这3个数字相同。在第一种状况中,中间数字(下标为2)位于后面是子数组;在第二种状况中,中间数字(下标为2)位于前面的子数组 中。所以,当两个指针指向的数字及它们中间的数字三者相同的时候,咱们没法判断中间的数字是位于前面的子数组中仍是后面的子数组中国,也没法移动两个指针 来缩小查找的范围。此时,咱们不得不采用顺序查找的方法。遍历

在把问题分析清楚后造成清晰的思路以后,咱们就能够把前面的代码修改成:

Java代码实现:

 

package cglib;

public class List1
{  
     public static int minInReversingList(int[] arr){  
            if(arr==null){
                 System.out.println("空数组");  
                return -1;  
            }  
            int leftIndex = 0;//3,4,5,1,2  
            int rightIndex = arr.length -1; //长度5,序号4
            int midIndex = leftIndex;  //将中间数索引初始化为第一个数索引,对应旋转后仍然是排序数组的状况,如旋转后仍旧是1,2,3,4,5
            while(arr[leftIndex]>= arr[rightIndex]){
                System.out.println("左索引数大于等于右索引数");
                System.out.println("arr[leftIndex]="+arr[leftIndex]);
                System.out.println("arr[rightIndex]="+arr[rightIndex]);
                if(rightIndex - leftIndex <= 1){
                    System.out.println("rightIndex="+rightIndex);
                    System.out.println("leftIndex="+leftIndex);
                    midIndex = rightIndex;  
                    System.out.println("midIndex="+midIndex);
                    break;  
                }  
                midIndex = (leftIndex+rightIndex)/2;  
                if(arr[leftIndex]== arr[rightIndex] && arr[midIndex]== arr[leftIndex]){  
                    System.out.println("只能顺序查找");
                    return MinInOrder(arr,leftIndex,rightIndex); //这三个数相同,无法分辨中间数属于左右哪一个数组,只能按顺序查找,找出最小值
                }  
                if(arr[midIndex] >= arr[leftIndex]){  
                    leftIndex = midIndex;  //3,4,5, 1,2.中间数大于最左边的数,说明最小数在中间数右边,左边索引就变成中间数索引
                }else if(arr[midIndex] < arr[rightIndex]){  
                    rightIndex = midIndex;  //5,1,2,3,4.中间数小于最右边的数,说明最小数在中间数左边,右边索引就变成中间数索引
                }  
            }  
            return arr[midIndex];  
        }  
        public static int MinInOrder(int[] arr,int leftIndex,int rightIndex){  
            int result = arr[leftIndex]; //1,0,1,1,1
            System.out.println("result="+result);
            System.out.println("rightIndex="+rightIndex);
            System.out.println("arr[leftIndex]="+arr[leftIndex]);
            for(int i = leftIndex +1;i<rightIndex;i++){  
                if(result> arr[i]){  
                    result = arr[i];  
                }  
            }  
            return result;  
        }  
        public static void main(String[] args){  
            
            int[] arr={3,4,5,1,2};
            //int[] arr={1,2,3,4,5};
            //int[] arr=null;
            //int[] arr={1,0,1,1,1};//{2,2,2,2,2,0,1,2,2};  
            System.out.println(minInReversingList(arr));  
              
        }
}

输出:

左索引数大于等于右索引数 arr[leftIndex]=3 arr[rightIndex]=2 左索引数大于等于右索引数 arr[leftIndex]=5 arr[rightIndex]=2 左索引数大于等于右索引数 arr[leftIndex]=5 arr[rightIndex]=1 rightIndex=3 leftIndex=2 midIndex=3 1

相关文章
相关标签/搜索