import com.google.common.base.Preconditions;
/**
* 数组的旋转:
*
* 概念:把一个数组最开始的若干元素搬到数组的末尾。
* 举例:数组{3,4,5,1,2}是数组{1,2,3,4,5}的一个旋转。
*
* 问题:输入一个递增排序的数组的一个旋转,输出该旋转数组的最小元素。
*
* 分析:
* 1)旋转数组的第一个元素大于等于最后一个元素。
* 2)旋转数组中包含了两个递增的序列,第二个递增序列中的第一个元素即该旋转数组的最小值。
* 3)我们可以根据旋转数组的中间元素来确定最小元素的位置:
* 1>若中间元素大于等于旋转数组的第一个元素,则说明最小值在旋转数组的右半部分;
* 2>若中间元素小于旋转数组的第一个元素,则说明最小值在旋转数组的左半部分;
* 3>故我们可以使用二分法来确定最小元素的位置。
* 4>需要注意一种特殊情况:当left指向的元素、right指向的元素、中间元素 这3个元素相等时,我们就无法确定最小的元素的位置,故此时只能使用顺序查找了。
*
* 说明:
* 1)直接遍历旋转数组即可找到最小元素,但是时间复杂度为O(n)
* 2)采用二分法,时间复杂度为O(logn)
*
*/
public class RotateArray {
public static int getMin(int rotateArray[]) {
Preconditions.checkArgument(null != rotateArray && rotateArray.length != 0);
int left = 0;
int right = rotateArray.length - 1;
int mid = left;
// 兼容特殊情况:若将递增排序数组最开始的0个元素搬到数组的末尾,即旋转数组与原数组相同的情况。
if (rotateArray[left] < rotateArray[right]) {
return rotateArray[left];
}
/**
* 当right-left==1时,表示left与right相邻,
* 即left为第一个序列的最后一个元素的下标,right为第二个序列的第一个元素的下标,故此时下标为right的元素即最小元素。
*/
while (right - left > 1) {
mid = (left + right) >> 1;
// 当3者相等时,无法确定最小值的范围,故使用顺序遍历的方式来查找left和right之间的最小值。
if (rotateArray[left] == rotateArray[right] && rotateArray[left] == rotateArray[mid]) {
int min = rotateArray[left];
for (int i = left; i <= right; i++) {
if (rotateArray[i] < min) {
min = rotateArray[i];
return min;
}
}
}
// 当3者不相等时,可以确定最小值的范围,故使用二分法来查找最小值。
if (rotateArray[mid] >= rotateArray[left]) { // 中间元素大于等于旋转数组的第一个元素
left = mid;
} else { // 中间元素小于旋转数组的第一个元素,即中间元素小于等于旋转数组的最后一个元素
right = mid;
}
}
return rotateArray[right];
}
public static void main(String[] args) {
int[] rotateArray = {3, 4, 5, 1, 2};
// int[] rotateArray = {1, 2, 3, 4, 5};
// int[] rotateArray = {1, 0, 1, 1, 1};
// int[] rotateArray = {1, 1, 1, 0, 1};
int min = getMin(rotateArray);
System.out.println(min);
}
}