剑指offer:旋转数组的最小数字

题目描述

一个递增排序的数组的一个旋转(把一个数组最开始的若干元素搬到数组的末尾,称之为数组的旋转),输出旋转数组的最小元素。

解题思路:

方法一:顺序查找,时间复杂度为O(n)

非递减数组旋转之后最小值,也就是寻找分界点,分界点前后都是非递减数组,分界点后面的非递减数组比分界点前面的数组都要小,因此对旋转数组按顺序查找,当出现后一个数比前一个小时,这个数就是最小值,若没有出现后一个数比前一个数小的情况,这说明这个数组所有的数都相等,返回数组第一个数即可。注意考虑数组为空的情况,返回0

代码如下:

import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array==null || array.length == 0) return 0;
int startIndex = 0, endIndex = array.length - 1;
// 不旋转的情况
if(array[endIndex] > array[startIndex])
return array[startIndex];
while(endIndex>0){
if(array[endIndex-1] > array[endIndex])
return array[endIndex];
endIndex--;
}
return array[startIndex];
}
}

方法二:二分查找,时间复杂度为O(logn)

旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素。

注意到实际上最小的元素就是两个子数组的分界线。本题目给出的数组一定程度上是排序的,因此我们试着用二分查找法寻找这个最小的元素。 
  
(1)我们用两个指针left,right分别指向数组的第一个元素和最后一个元素。按照题目的旋转的规则,第一个元素应该是大于最后一个元素的(没有重复的元素)。 
但是如果不是旋转,第一个元素肯定小于最后一个元素。 
  
(2)找到数组的中间元素。 
中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。 
移动之后,第一个指针仍然位于前面的递增数组中。 
中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。 
移动之后,第二个指针仍然位于后面的递增数组中。 
这样可以缩小寻找的范围。 
  
(3)按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。 

特殊情况考虑:旋转0个元素、三个指针相等情况(10111, 11101),此时不得不采用顺序查找

//
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
// 判断特殊情况
if (array==null||array.length==0)
return 0;
int firstIndex = 0;
int lastIndex = array.length - 1;
// 这里初始化mid为0的原因是,如果旋转0个元素那么最小值就是第一个元素
int midIndex = 0;
// 如果旋转0个元素则直接跳出循环
while (array[firstIndex] >= array[lastIndex]){
if (firstIndex+1==lastIndex){
midIndex = lastIndex;
break;
}
// 三指针相等时,只能顺序查找,这种情况非常容易漏,要非常细心
if (firstIndex==lastIndex && firstIndex==midIndex){
return MinInOrder(array, firstIndex, lastIndex);
}
midIndex = (firstIndex + lastIndex) / 2;
if (array[firstIndex] <= array[midIndex]){
firstIndex = midIndex;
}
else{
lastIndex = midIndex;
}
}
return array[midIndex];
}
public int MinInOrder(int [] array, int low, int high){
int ret = array[low];
for (int i=low+1; i<high; i++){
if (array[i] < ret){
ret = array[i];
}
}
return ret;
}
}

方法三:二分查找变种

这种方法是如果存在相同的数字,不要采用顺序查找,而是选择缩小一个数的范围继续二分,相较于上一种方法有一定的优化!

需要考虑三种情况: 
(1)array[mid] > array[high]: 
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。 
low = mid + 1 
(2)array[mid] == array[high]: 
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边 
还是右边,这时只好一个一个试 , 
high = high - 1 
(3)array[mid] < array[high]: 
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左 
边。因为右边必然都是递增的。 
high = mid 
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字 
比如 array = [4,6] 
array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ; 
如果high = mid - 1,就会产生错误, 因此high = mid 
但情形(1)中low = mid + 1就不会错误

public class Solution {
public int minNumberInRotateArray(int [] array) {
int low = 0 ; int high = array.length - 1;
while(low < high){
int mid = low + (high - low) / 2;
if(array[mid] > array[high]){
low = mid + 1;
}else if(array[mid] == array[high]){
high = high - 1;
}else{
high = mid;
}
}
return array[low];
}
}
posted @ 2018-01-08 09:01  小丑进场  阅读(178)  评论(0)    收藏  举报