题目:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
第一思路:
刚拿到这道题我相信大多数人和我一样想到了时间复杂度为O(n)的查找算法,即遍历一遍数组找到自己满足题意的最小值元素。可以如果这样做,就违背了题意旋转数组,即没有利用旋转数组中的两部分有序原则。故需再思考。
最优思路:
(1)旋转数组实际上可以划分为两个排序的子数组,而且前面的子数组元素都大于或者等于后面的子数组的元素;(2)最小的元素刚好是这两个字数组的分界线;因为在排序的数组中我们可以用二分查找法实现O(logn)的查找。故旋转数组找最小元素值也可以采取二分查找的思路。
首先我们定义两个指针p1指向数组的第一个元素和p2指向数组的最后一个元素,接着我们找到中间的元素。如果该元素位于前面递增数组中,它应该大于或者等于第一个指针指向的元素,我们将p1指向中间元素;如果中间元素位于后面的递增数组中,那么它应该小于或者等于第二个指针指向的元素,我们把p2指向中间元素。不管移动p1还是p2,查找的范围都会缩小为原来一半,接下来利用更新后的两个指针重复上面的查找。按照上面的思路,p1始终在前面递增数组,p2始终在后面递增数组,最终p1会指向前面子数组的最后一个元素,第二个指针会指向后面子数组的第一个元素,即最小元素,结束循环。
有一特例是p1、p2和中间值都相等,这时候就老老实实循环遍历一次数组,找到最小值。
代码:
public class Main {
//一般情况时的查找算法
public int Min(int arrays[]){
if(arrays == null){
try {
throw new Exception("Invalid parameters");
} catch (Exception e) {
e.printStackTrace();
}
}
int index1 = 0;
int index2 = arrays.length - 1;
int indexMid = index1;
while(arrays[index1] > arrays[index2]){
if(index2 - index1 == 1){ //如果差值为1,则index2下标的数一定是最小数
indexMid = index2;
break;
}
indexMid = ( index1 + index2 ) / 2; //取中间值
if(arrays[index1] == arrays[index2] && arrays[indexMid] == arrays[index1]){ //特例
return MinInOrder(arrays);
}
if(arrays[indexMid] >= arrays[index1]){ //如果中间值大于index1对应的值,则修改index1的值为中间值
index1 = indexMid;
}else if(arrays[indexMid] <= arrays[index2]){ //否则修改index2的值为中间值
index2 = indexMid;
}
}
return arrays[indexMid]; //indexMid对应的一定是最小值
}
//特例时的查找算法
private int MinInOrder(int[] arrays) {
int result = arrays[0];
for (int i = 0; i < arrays.length; i++) {
if(result > arrays[i]){
result = arrays[i];
}
}
return result;
}
//测试
public static void main(String[] args) {
int num1[] = {4, 5, 6, 7, 1, 2, 3};
int num2[] = {1, 0, 1, 1, 1};
int num3[] = {1, 1, 1, 0, 1};
Main m1 = new Main();
System.out.println(m1.Min(num3));
System.out.println(m1.Min(num2));
System.out.println(m1.Min(num1));
}
}
小结:
这道题是二分查找的变形,考查二分查找。以及接收新事物的理解能力与转化能力。