题解 | #旋转数组的最小数字#
旋转数组的最小数字
http://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba
这道题花了蛮长的时间,明确知道使用二分法,但是在决策:什么时候当下的mid是最小值?选择左边还是右边?这两个最为纠结。
考虑两种情况:
一种是顺序的:[1, 2, 3, 4, 5, 6] 分成两部分[1, 2, 3] 4 [5, 6]
一种是翻转的:[6, 1, 2, 3, 4, 5] 分成两部分[6, 1, 2] 3 [4, 5]
可以看到,咱们虽然看不到区间内最小的值,例如[6, 1, 2]最小为1,但是6到2之间一定小于3,肯定包含比3小的数,例如2,所以选择端点小的区间就可以解决“选择左边还是右边”的问题。
[1, 2, 3]最小的端点是1,小于4;
[6, 1, 2]最小的端点是2,小于3;
那么同样的,如果mid的值小于左边也小于右边的值,则中间mid是最小的,直接返回。
上面的方式忽略掉了一个问题,踩到了最大的坑,就是重复值问题:
[5,1,2,2,2,3] 分拆为两部分:[5,1,2] 2 [2,3],这时候无论你选择直接返回2还是选择左右两边都是有问题的。
个人的做法是,在选择好mid值之后,分拆左右两边的时候会左右移动去除重复值,上述例子拆为[5,1] 2 [3]。
所以极限情况下会将时间复杂度为On。
import java.util.ArrayList; public class Solution { public int minNumberInRotateArray(int [] array) { if (array.length == 0) { return 0; } return find(array, 0, array.length - 1); } private int find(int []array, int begin, int end) { if (begin == end) { return array[begin]; } int mid = (end + begin) / 2; int midValue = array[mid]; int leftBegin = begin; int leftEnd = Math.max(begin, mid - 1); while(leftEnd - 1 >= begin) { if(array[leftEnd] == midValue) { leftEnd--; } else { break; } } int leftMin = Math.min(array[leftBegin], array[leftEnd]); int rightBegin = Math.min(end, mid + 1); while(rightBegin + 1 <= end) { if(array[rightBegin] == midValue) { rightBegin++; } else { break; } } int rightEnd = end; int rightMin = Math.min(array[rightBegin], array[rightEnd]); if (midValue < leftMin && midValue < rightMin) { return midValue; } if (leftMin < rightMin) { return find(array, leftBegin, leftEnd); } else { return find(array, rightBegin, rightEnd); } } }