旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出该旋转数组的最小元素。
例如:数组{3,4,5,1,2} 为{1,2,3,4,5}的一个旋转,该数组的最小值为1.
本题最直观的解法就是从头遍历数组,已知是递增排序的数组,那么旋转后的数组,当第一次遇到数组中某一位小于它的前一位时,它即是最小值,此时的时间复杂度为O(n).此处我们不做代码实现,感兴趣的小伙伴可以自行尝试。
实际上,旋转后的数组可以划分为两个递增排序的子数组,而且前面子数组的元素都大于或者等于后面子数组的元素。并且最小值刚好是这两个子数组的的分界线。所以可以使用二分查找来实现时间复杂度O(logn)。
二分法,先找到中间元素middle,如果middle位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素,且最小值位于后一递增子数组,这样我们可以把第一个指针指向middle;同理,如果middle位于后面的递增子数组,那么它应该小于或者等于middle;那我们可以将第二个指针指向middle。不论是移动第一个指针或者是第二个指针,这样我们都可以缩小查找范围,同时我们也需要考虑数组是否包含相同数字,以及两个指针和middle同时指向同一个元素的临界情况;此时,只能通过顺序对比查找了。
上代码。
public static Integer findMin(int[] nums) {
//常规判空
if (nums == null || nums.length == 0) {
return null;
}
int start = 0;
int end = nums.length - 1;
int middle = 0;
while (nums[start] >= nums[end]) {
//刚好相差1,即end为最小值
if (end - start == 1) {
middle = end;
break;
}
middle = (start + end) >> 1;
//如果start,end ,middle 三个值均相等,则无法通过移动指针,只能顺序查找
if (nums[middle] == nums[start] && nums[middle] == nums[end]) {
for (int i = start + 1; i <= end; ++i) {
if (nums[start] > nums[i]) {
return nums[i];
}
}
}
//如果start<=middle,则说明最小值在后一递增子数组
if (nums[middle] >= nums[start]) {
start = middle;
}
//如果end<=middle,则说明最小值在前一递增子数组
if (nums[middle] <= nums[end]) {
end = middle;
}
}
return nums[middle];
}
//测试用例
public static void main(String[] args) {
int nums[] = new int[]{3, 4, 5, 1, 2};
System.out.println(FindMin.findMin(nums));
int nums1[] = new int[]{1, 0, 1, 1, 1};
System.out.println(FindMin.findMin(nums1));
int nums2[] = new int[]{1, 1, 1, 0, 1};
System.out.println(FindMin.findMin(nums1));
}
}