12. 旋转数组的最小数字
问题描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
很容易我们想到的是,从头到尾遍历一次数据,我们就找出了最小值,时间复杂度是O(n),但是我们一点都没有用到旋转数组的特性.
分析
- 旋转数组,其实可以划分为两个排序的子数组,而且前面的子数组的元素都大于后面子数组的元素
- 最小值刚好就是这两个子数组的分界线
- 我们可以尝试用二分查找来实现(时间复杂度 O(logn))
代码如下:
/**
* Class day12 ...
*
* @author LiJun
* Created on 2018/12/28
*/
public class day12 {
public static int Min(int[] numbers) {
// 参数有效性验证
if (numbers == null || numbers.length <= 0) {
return Integer.MIN_VALUE;
}
// 二分法的基本实现就是一个指向头 一个指向尾
int start = 0;
int end = numbers.length - 1;
// 设置medium的原因是一旦数组中的第一个数字 小于最后一个数字,说明这个数组是排序的
// 直接返回第一个数字就行
int medium = start;
while (numbers[start] >= numbers[end]) {
// 如果start 和 end 执行相邻的数字
// 则 start 执行第一个递增数列的最后一个数字
// end 指向第二个递增数列的第一个数字
if (end - start == 1) {
medium = end;
break;
}
medium = (start + end) / 2;
// 这里是特殊情况 如果这三者相等的话,就只能老老实实的顺序查找了
if(numbers[start] == numbers[medium] && numbers[medium] == numbers[end]){
return GetMinInOrder(numbers, start, end);
}
if(numbers[medium] >= numbers[end]){
start = medium;
}else if(numbers[medium] <= numbers[end]){
end = medium;
}
}
return numbers[medium];
}
public static int GetMinInOrder(int[] numbers, int start, int end) {
int min = numbers[start];
for(int i = start+1; i <= end; i++){
if(numbers[i] < min){
min = numbers[i];
}
}
return min;
}
public static void main(String[] args) {
// 基础测试
int[] numbers = {3,4,5,1,2};
System.out.println(Min(numbers));
numbers = new int[]{1, 0, 1, 1, 1};
System.out.println(Min(numbers));
}
}
在这我需要解释下,如果numbers[start] < numbers[end]
这就说明,这区间已经是排序好了的,无须查找了。
其次,有种情况,那就是 根据旋转数组的特性,第一个数字总是大于或者等于最后一个数字,那么我们现在想想,如果我们把 0 个数据搬到后面去 这时候第一个数据就是最小的,这也就是我们为什么把 medium
初始为 start
的值的原因。
但是我们也有种情没考虑到,当 numbers[start] = numbers[end] =numbers[medium]
的时候, 这个时候,我们应该怎么办呢。 例如: {1, 0, 1, 1, 1} 之类的,我们可以得到 numbers[medium] = 1
这三个值都相等的时候,我们怎样二分。我们只能老老实实的顺序遍历找出答案了。