题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 {3, 4, 5, 1, 2}{1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1

    最简单的方法就是直接扫描一遍数组,找到数组中最小的数字,这样做它的时间复杂度是
    旋转数组的最小数字 - 图1#card=math&code=O%28n%29&height=20&width=36)
    旋转数组的最小数字 - 图2
    观察到最小值在第一部分和第二部分的分界处,且在第二部分,另外可以观察到,由于第二部分是数组的前半部分搬到末尾的,所以第二部分中的值是小于等于第一部分中的值。

    对于顺序查找问题,我们可以使用二分查找法。我们使用两个指针分别指向第一部分的开头和第二部分的结尾,然后将这两个值与数组中间的值进行比较,如果中间的值比第一部分的开头大,说明中间的值还在第一部分,我们将第一部分的开头指针移动到中间;同理,如果中间值比第二部分末尾的值小,则中间的值还在第二部分,则将第二部分末尾的指针移动到中间。以此类推,直到两个指针相差一个距离,则到了分界处,取第二部分指针所对应的值,该值即为最小值。图示如下:
    旋转数组的最小数字 - 图3
    但是上面的方法却又一个特例,即中间值与两个指针所指的值相等时,则无法知道如何移动指针
    旋转数组的最小数字 - 图4
    上面两个数组都是 {0, 1, 1, 1, 1} 的旋转,并且中间的值与两个指针指向的值相同,但是中间值一个在第一部分,一个在第二部分,所以我们这时是无法知道如何移动指针,碰到这种情况,我们只能顺序查找了,代码如下

    1. public class Min {
    2. public static int min(int[] arr) {
    3. int start = 0;
    4. int end = arr.length - 1;
    5. int mid = 0;
    6. // 如果 start 的值小于 end,说明数组没有进行旋转,则返回数组的第一个值,这就是 mid = 0 的原因
    7. while (arr[start] >= arr[end]) {
    8. // 如果两个指针相差 1 个距离,则返回第二个指针指向的值
    9. if (end - start == 1) {
    10. mid = end;
    11. break;
    12. }
    13. mid = (start + end) / 2;
    14. // 如何中间的值和两个指针的值相等,则进行顺序查找
    15. if (arr[start] == arr[end] && arr[start] == arr[mid]) {
    16. return minInOrder(arr, start, end);
    17. }
    18. // 如果中间的值比 start 的值大,则将 start 移至中间
    19. if (arr[mid] >= arr[start]) {
    20. start = mid;
    21. } else if (arr[mid] < arr[end]) {
    22. // 否则中间的值比 end 小,则将 end 移至中间
    23. end = mid;
    24. }
    25. }
    26. return arr[mid];
    27. }
    28. // 顺序查找的代码
    29. public static int minInOrder(int[] arr, int start, int end) {
    30. int minResult = arr[start];
    31. for (int i = start; i <= end; i++) {
    32. if (minResult > arr[i]) {
    33. minResult = arr[i];
    34. }
    35. }
    36. return minResult;
    37. }
    38. public static void main(String[] args) {
    39. int[] arr = {3, 4, 5, 1, 2};
    40. System.out.println(min(arr)); // 1
    41. }
    42. }