package main /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ func minNumberInRotateArray( nums []int ) int { // write code here // 二分法 left, right := 0, len(nums)-1 for left < right { mid := (right-left)/2 + left // “夹”的思想,我们知道位于后面的旋转数组部分一定是小于前面的部分的 // 因此我们可以以最右侧元素作为基准,找到第一个比它小的元素,这时我们一直在往右边靠 // 一旦找到说明我们已经进入了旋转数组部分,这时我们需要往左靠,即更新 right // 而如果我们找到的值 == right 处值,也是向左靠,只不过是移动一个位置 // 最终以 left 值作为边界,满足条件的最左侧边界值。 if nums[mid] > nums[right] { left = mid + 1 } else if nums[mid] < nums[right] { right = mid } else { right-- } } return nums[left] }
如上图所示,原本位于开头的 [1, 2] 部分被搬移到了末尾,将原数组分割成两个非递减的数组。
为了方便,我们将 [1, 2] 称为迁移部分, [3, 4, 5] 称为未迁移部分,可得出如下结论:
- 未迁移部分元素值大于等于迁移部分元素值;
- 旋转数组的最小数字是迁移部分的最左侧元素的值;
我们根据上述两个结论,我们可以使用二分法找比迁移部分的最右侧元素小的元素值:
- 如果 mid 处的元素值大于迁移部分最右侧的元素值,那么说明 mid 处元素仍然位于未迁移部分,迁移部分一定在 mid 右侧,所以我们更新 left 指针;
- 如果 mid 处的元素小于迁移部分最右侧的元素值,那么说明 mid 处元素已经处于迁移部分中了(只有迁移部分元素才可能小于最右侧元素),这时候我们知道 mid 可能是迁移部分的最小数字,但也有可能在 [left, mid)之间,所以我们更新 right 指针。
- 如果 mid 处的元素等于迁移部分最右侧的元素值,那么我们唯一知道的是 right 处一定不可能是最小数字了(因为 mid 位于 right 左侧),所以我们将 right 减 1。至于为什么不让 right 直接更新为 mid,我们后面会有一个图示专门说明这种特殊情况。
我们举三个例子说明上述代码逻辑:
例 1
初始时,left = 0 ,right = 4 ;
mid = 2,arr[2] > arr[4] ,说明 mid 所指向的元素不在迁移部分,此时我们继续往右边找,更新 left = mid + 1 = 3;
mid = 3,arr[3] < arr[4] ,说明 mid 所指向的元素已经在迁移部分了,我们继续往左侧找左边界,right = mid = 3;
left = 3, right = 3, mid = 3 ,arr[3] = arr[3] , right = right - 1 = 2 ,循环结束,返回 left 指向的值,return 1。
例 2
例 3