剑指 Offer 11. 旋转数组的最小数字
剑指 Offer 11. 旋转数组的最小数字

注意,这里并没有告诉我们数组是否有序,而是可能有序,因此我们可以和选择字符串那题联系一下,找到分别有序的两个部分,将两部分分别反转后再整体反转,即可得到一个完全有序的数组。再返回数组的第一个数字即可。
class Solution {
public int minArray(int[] numbers) {
int idx = 0, n = numbers.length;
if(n == 1) return numbers[0];
// 找出两个有序区间的分割点
for(int i = 1; i < n; i++) {
if(numbers[i] < numbers[i - 1]) {
idx = i;
break;
}
}
// 反转区间
reverse(numbers, 0, idx - 1);
reverse(numbers, idx, n - 1);
reverse(numbers, 0, n - 1);
return numbers[0];
}
private void reverse(int[] numbers, int s, int e) {
while(s < e) {
int tmp = numbers[s];
numbers[s] = numbers[e];
numbers[e] = tmp;
s++;
e--;
}
}
}
时间复杂度为\(O(n)\),空间复杂度为\(O(1)\)。
若不利用有序条件,直接枚举每一个位置也是一种可行解法,时间复杂度为\(O(n)\),空间复杂度为\(O(1)\):
class Solution {
public int minArray(int[] nums) {
int ans = Integer.MAX_VALUE;
for(int num : nums) ans = Math.min(ans, num);
return ans;
}
}
二分的做法本菜鸡没有想到🤣,去评论区学习了一下大佬的二分做法:
public class Solution {
public int minArray(int[] numbers) {
int l = 0, r = numbers.length - 1;
while (l < r) {
int mid = ((r - l) >> 1) + l;
//只要右边比中间大,那右边一定是有序数组
if (numbers[r] > numbers[mid]) {
r = mid;
} else if (numbers[r] < numbers[mid]) {
l = mid + 1;
//去重
} else r--;
}
return numbers[l];
}
}
思路在于:若中点值小于 nums[right],说明 (mid,right] 区间内正确升序,无旋转点,旋转点在 [left,mid]之间,若中点值大于 nums[right],说明(mid,right] 区间内存在旋转点导致了降序,此时令 left=mid+1 继续循环,若中点值刚好与 nums[right]相等,说明存在重复元素,故 right 向左移一(多)位即可。(去重)值比较一直采用了 nums[right],因为它与可以配合 nums[mid] 正确地进行区间缩减。

浙公网安备 33010602011771号