双指针,二分法

题目链接-来源:力扣(LeetCode)

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

示例 1:

输入:[3,4,5,1,2]
输出:1

思路:

最直观的思路就是,原数组为非递减数组,则旋转以后可以分解为两个非递减的子数组,我们依次两两比较数组中的数字,一旦出现 xi > xj (i < j) 的情况,输出 xj 即可。
进一步优化:
对有序数据的查找,容易想到二分法。为了直观说明,假设原数组为 [x0, x1, x2, x3, x4] 我们有 x1 <= x2 <= x3 <= x4,经过旋转后得 [x2, x3, x4, x0, x1],注意到一定有 最右侧元素 <= 最左侧元素,在本例中即 x1 <= x2,所找元素就应该顺着 x1 向左进行。
我们用头指针 left 指向数组最开始元素,用尾指针 right 指向数组末尾元素,则两指针的中间指针 mid = (left + right) / 2 有如下可能:

  • nums[mid] (> nums[left]) > nums[right],即例子中的 x3,x4,那么所找元素就应该在mid的右边,于是令 left = mid + 1。
  • nums[mid] < nums[right],即例子中的 x0,当然此时我们并不知道它就是最小元素,我们认为最小元素可能出现在其左边,于是令 right = mid。
  • 对于相等情况,说明从mid到right很长距离上,甚至整个数组的元素有可能全部相等,我们慢慢缩小 right,否则可能会出现最小元素夹在若干个相等元素之间,被我们跨过的情况。

时间复杂度:O(log n)
空间复杂度:O(1)

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int minArray(int[] numbers) {
int n = numbers.length;
int left = 0;
int right = n - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (numbers[mid] < numbers[right]) {
right = mid;
} else if (numbers[mid] > numbers[right]) {
left = mid + 1;
} else {
right--;
}
}
return numbers[left];
}
}