剑指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]; } }
浙公网安备 33010602011771号