需求:
假设一个旋转排序的数组其起始位置是未知的(比如0 1 2 4 5 6 7 可能变成是4 5 6 7 0 1 2)。你需要找到其中最小的元素。数组中有可能包含重复元素。
分析:
1、旋转的概念
把一个数组最开始的若干元素搬到数组的末尾,叫做数组的旋转。如果是排序数组的旋转,那么旋转之后的数组有可能是排序的,也有可能包含两个有序子数组,左边的有序数组中元素都大于等于右边的有序数组中的元素。
2、简易解法
1)对数组进行排序,返回角标0的元素
2)利用集合的Collections.min(List)方法求最小值
3)从前向后遍历数组,如果当前元素大于后面的元素,那么后面的元素即为最小值
3、二分法
传统的二分法都是应用于排序数组,而旋转数组在一定程度上也是有序的,因此可以试图按照二分法的思想求解最小值。求数组的最小值,就是求左右两个子数组的临界位置,因此可以设置两个变量,index1和index2,分别用来指示左右两子数组中的元素,当两个变量差值是1的时候,说明第二个变量指示的值是最小值。当两个变量差值大于1,那么求中间值mid,如果中间值大于等于index1指示的值,那么说明中间值在左子数组中,更新index1为中间值所在的角标,否则,更新index2为中间值所在角标。
有个特殊情况,当index1、index2和mid值相同,比如11101和10111,如果index1=0,index2=4,两个数组中中间值都是1,这时三个值相同,如果我们认为中间值大于等于index1对应的值,即中间值在左子数组中,那么对于第二个旋转数组是不对的。所以这种情况,只能顺序查找。
代码:
public class Solution {
/*
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
/*
//思路一:借助数组排序来做
if(nums == null || nums.length <= 0){
//抛出的异常表明向方法传递了一个不合法或不正确的参数。
throw new IllegalArgumentException("invalid parameters");
}
Arrays.sort(nums);
return nums[0];
*/
/*
//思路二:从前向后遍历,找到第一个比左边小的元素,即为最小,如果找不到,就返回第一个元素
if(nums == null || nums.length <= 0){
throw new IllegalArgumentException("invalid parameters");
}
for(int i = 0; i < nums.length-1; i++){
if(nums[i] > nums[i+1]){
return nums[i+1];
}
}
return nums[0];
*/
/*
//思路三:借助集合的Collections.min()来做
if(nums == null || nums.length <= 0){
throw new IllegalArgumentException("invalid parameters");
}
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < nums.length; i++){
list.add(nums[i]);
}
return Collections.min(list);
*/
/*
//思路四:初始化最小值,遍历求最小值
if(nums == null || nums.length <= 0){
throw new IllegalArgumentException("invalid parameters");
}
int min = nums[0];
for(int i = 1; i < nums.length; i++){
if(nums[i] < min){
min = nums[i];
}
}
return min;
*/
//二分法
if(nums == null || nums.length <= 0){
throw new IllegalArgumentException("invalid parameters");
}
//定义两个指针,指向数组的头和尾
int index1 = 0, index2 = nums.length-1;
//定义指针,指向中间值
int indexMid = 0;//初始化为0
//当第一个指针指向的元素大于等于第二个指针指向的元素,说明两个指针分别在两个子数组中
while(nums[index1] >= nums[index2]){
//当两个指针指向的元素是相邻的,那么第二个指针对应的元素是最小值
if(index2-index1 == 1){
indexMid = index2;
break;
}
//当两个指针不是相邻的,需要求中间值,然后和两指针元素比较大小
indexMid = (index1 + index2) / 2;
//当三个指针元素相同,那么需要顺序查找
if(nums[index1] == nums[index2] && nums[index1] == nums[indexMid]){
return minInorder(nums, index1, index2);
}
//当中间值大于第一个指针元素,说明中间值在第一个子数组中
if(nums[indexMid] >= nums[index1]){
index1 = indexMid;
}
else /*if(nums[indexMid] <= nums[index2])*/{
index2 = indexMid;
}
}
return nums[indexMid];
}
//求arr数组中角标index1到index2之间的最小值
public int minInorder(int[] arr, int index1, int index2){
int min = arr[index1];
for(int i = index1+1; i <= index2; i++){
if(arr[i] < min){
min = arr[i];
}
}
return min;
}
}
拓展:
如果没有重复元素,那么不需要进行index1、index2、indexMid元素的比较。
代码:
public class Solution {
/*
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
if(nums == null || nums.length <= 0){
throw new IllegalArgumentException("invalid parameters");
}
int index1 = 0, index2 = nums.length-1, indexMid = 0;
while(nums[index1] > nums[index2]){
if(index2-index1 == 1){
indexMid = index2;
break;
}
indexMid = (index1+index2)/2;
if(nums[indexMid] > nums[index1]){
index1 = indexMid;
}
else{
index2 = indexMid;
}
}
return nums[indexMid];
}
}