剑指offer--旋转数组的最小数字(详细分析+特殊情况讨论)

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

分析:
旋转以后的数组实际上可以划分为两个排序的子数组,最小的数为这两个排序数组的分界线,在排序的数组我们可以用二分实现复杂度低的查找,此题的数组在一定程度上也是排序的,我们可以试着用二分法的思路来寻找最小元素。
和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。接着我们可以找到数组中间的元素。如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一个指针指向该中间元素,这样可以缩小寻找的范围。移动之后的第一个指针仍然位于前面的递增子数组。
同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样也可以缩小寻找的范围。移动之后的第二个指针仍然位于后面的递增子数组。

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

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

我们以前面的数组为例[3,4,5,1,2] 为例,我们先把第一个指针指向第0个元素,把第二个指针指向第4个元素,位于两个指针中间(在数组中的下标是2)的数字是5,它大于第一个指针指向的数字。因此中间数字5一定位于第一个递增子数组,并且最小的数字一定位于它的后面。因此我们可以移动第一个指针,让它指向数组的中间,此时位于这两个指针中间(在数组中的下标是3)的数字是1,它小于第二个指针指向的数字。因此这个中间数字1一定位于第二个递增字数组,并且最小的数字一定位于它的前面或者它自己就是最小的数字。因此我们可以移动第二个指针,让它指向两个指针中间的元素,即下标为3的元素,此时两个指针的距离是1,表明第一个指针已经指向第一个递增子数组的末尾,而第二个指针指向第二个递增子数组的开头。第二个子数组的第一个数字就是最小的数字,因此第二个指针指向的数字就是我们查找的结果。

在这里插入图片描述

根据以上思路我们可以写出代码

int minArray(int* numbers, int numbersSize){
    
    
    if(numbers == NULL || numbersSize <= 0) return 0;
    
    if(numbersSize == 1) return numbers[0];

    int index1 = 0;
    int index2 = numbersSize-1;
    int midindex = index1;

    while(numbers[index1] >= numbers[index2]){
    
    
        if(index2- index1 == 1){
    
    
            midindex = index2;
            break;
        }
        midindex = (index1+index2)/2;

      ---------------添加代码------------------
        if(numbers[midindex] >= numbers[index1])
        index1 = midindex;
        if(numbers[midindex] <= numbers[index2])
        index2 = midindex;
    }
    return numbers[midindex];

}

这样我们就完成了任务吗?答案当然是没有!QAQ
第一种特殊情况:
题目说把数组前面的若干元素搬到数组的后面,当然就包括0个元素的时候,这是数组第一个数字就是最小的元素,直接返回,这也就是上面代码中 把midindex 赋值为开始index1的原因 ,判断数组第一个数字小于最后一个数字以后,直接返回第一个数字。
第二种特殊情况
我们再仔细分析下标为index1和index2的两个数相同的情况。在前面的代码中,当这两个数相同,并且它们中间的数字(midindex指向的数字)也相同时,我们把midindex赋值给 index1,也就是认为此时最小的数字位于中间数字的后面。是不是一定这样?

我们来看一个栗子:
在这里插入图片描述

下面这两个数组是第一个数组的旋转,但是我们却无法确定中间的1是属于第一个递增子数组还是第二个递增子数组。
在这两种情况中,第一个指针和第二个指针指向的数字都是1,并且两个指针中间的数字也是1,这3个数字相同。在第一种情况中,中间数字(下标为2)位于后面的子数组;在第二种情况中,中间数字(下标为2)位于前面的子数组。因此,当两个指针指向的数字及它们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组还是后面的子数组,也就无法移动两个指针来缩小查找的范围。此时,我们不得不采用顺序查找的方法。

在分析整理好所有思路,我们对上面的代码进行修改。
把下面的代码添加到上述位置

 if(numbers[index1] == numbers[index2] && numbers[index1] == numbers[midindex]){
    
    
            int result = numbers[index1];
            for(int i = index1+1;i <= index2;i++){
    
    
                if(numbers[i] < result)
                result = numbers[i];
            }
            return result;
        }

与其感慨路途遥远,不如马上出发!加油!
内容参考:剑指offer

Guess you like

Origin blog.csdn.net/scarificed/article/details/120274895