题目描述(引自剑指offer)
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
又例如{1,0,1,1,1}和{1,1,1,0,1}都可以看成是递增排序数组{0,1,1,1,1}的旋转,该数组的最小值为0。
菜鸡与大佬的对话
题目分析
菜鸡拿到题目,发现题目定义了一个概念,称为数组的旋转。而本题研究的对象是有序数组的旋转。菜鸡觉得这道题目颇为简单,只要遍历数组array,如果存在array[i] < array[0],那么array[i]一定就是最小值,否则array[0]就是最小值。可是菜鸡转念一想,如果数组旋转的元素过多,需要遍历的数组元素就越多,最坏情况的时间复杂度是O(n),菜鸡震惊于自己的菜。
忽然间,菜鸡想起了大佬说过的话,针对有序数组的查找,可以使用二分查找。虽然现在数组并不一定是完全有序的,但菜鸡的第六感告诉他,这道题和二分查找脱不了干系。一番思考之后,他发现有序数组旋转之后,数组被分割成a,b两段有序数组,而且a段最小值大于等于b段最大值。特别的,有序数组是有序数组旋转的一个特例!
进一步考虑之后,菜鸡发现问题的难点在于,旋转前的数组是非递减的,而不是严格递增的,这说明数组中可能存在重复的元素,所以不能生搬硬套二分法,针对某些特殊情况要特殊处理。
菜鸡觉得自己的脑袋瓜灵光得很,他决定用Java代码把自己的心路历程描绘出来。
代码实现
public class Solution {
public int minNumberInRotateArray(int[] rotateArray) {
// 判断参数是否合法
checkParams(rotateArray);
// 申请三个变量为二分查找做准备
int start = 0;
int end = rotateArray.length - 1;
int mid = 0;
// 若rotateArray[start] < rotateArray[end],则数组未旋转
// 若rotateArray[start] > rotateArray[end],则数组已旋转
// 若rotateArray[start] == rotateArray[end],则数组可能旋转
while (rotateArray[start] >= rotateArray[end]) {
// 循环退出条件
if (end - start == 1) {
mid = end;
break;
}
// 先减后加防止int溢出,右移位运算代替除法提高效率
mid = start + ((end - start) >> 1);
// 特殊情况,旋转数组中start,end,mid三个位置的值相等
// 无法判断mid位置的值位于a段或是b段
// 需要进行特殊处理,可以进行顺序查找,也可进行递归二分查找
if (rotateArray[mid] == rotateArray[start]
&& rotateArray[mid] == rotateArray[end]) {
// 顺序查找最小值
return getMinBySequentialSearch(rotateArray, start, end);
}
// rotateArray[mid]位于a段,最小值位于mid之后
if (rotateArray[mid] >= rotateArray[start]) {
start = mid;
}
// rotateArray[mid]位于b段,最小值位于mid或mid之前
else {
end = mid;
}
}
return rotateArray[mid];
}
// 判断参数是否合法
private void checkParams(int[] array) {
if (array == null || array.length == 0) {
// 菜鸡自定义的异常枚举类,详情可访问文末相关链接
throw new CustomExceptionEnum.ILLEGAL_PARAM.exception();
}
}
// 顺序查找最小值
private int getMinBySequentialSearch(int[] array, int start, int end) {
// 判断参数是否合法
checkParams(array);
int min = array[start++];
for (int i = start; i < end; i++) {
// 根据旋转数组的特点,第一个小于min的值一定为最小值
if (array[i] < min) {
return array[i];
}
}
return min;
}
}
代码写罢之后,菜鸡发现,当数组为严格递增的数组时,上述代码的时间复杂度为O(logn),空间复杂度为O(1);而随着数组中重复元素的增加,进行顺序查找的概率会增加,最坏的情况下,时间复杂度为O(n),空间复杂度仍为O(1)。
第一次修炼马上就要结束了,菜鸡有了些许收获,他希望通过一次次的修炼,可以离理想越来越近,离菜越来越远……
相关链接
菜鸡自定义的异常枚举类
https://github.com/wjgdhj/CustomUtils/tree/master/src/main/java/com/noob/exception