剑指offer 11 旋转数组的最小数字 LeetCode154
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2]
为 [1,2,3,4,5]
的一个旋转,该数组的最小值为1
。
注意数组中可能存在重复的元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0
注意:
本题与主站 154 题相同:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/
面试小提示
如果面试题要求在排序的数组(或者部分排序的数组)中查找一个数字或者统计某个数字出现的次数,那么我们的第一反应应该就是二分查找算法。而不是从头到尾遍历(太low
,耗时),那样写出来了面试官也不屑的。
解法一:从头至尾遍历
为了扩充思路,这里我还是将从头至尾遍历
的方法写出来了,毕竟在其他非排序的数组时,该方法还是很有用的,如股票买卖
的初级问题就用到了从头至尾遍历
。既然是要找到最小的数字,那就用一个变量记录最小的数字即可,不断更新这个最小值,数组遍历结束时返回该最小值。
class Solution {
public int minArray(int[] numbers) {
if(numbers == null || numbers.length == -1) return 0;
int lowest = numbers[0];//用于记录最小值
for(int i = 1; i < numbers.length; i++){
if(numbers[i] < lowest){
lowest = numbers[i];//更新最小值
}
}
return lowest;
}
}
解法二:LeetCode官方给出的暴力解法
利用分界点左边的数肯定比右边的数小解题,如下图
class Solution {
public int minArray(int[] numbers) {
if(numbers == null || numbers.length == -1) return 0;
int lowest = numbers[0];//用于记录最小值
for(int i = 1; i < numbers.length; i++){
if(numbers[i-1] > numbers[i]){
lowest = numbers[i];//更新最小值
break;//已经找到最小值,可以退出循环了,小小的优化
}
}
return lowest;
}
}
解法三:二分查找
题目中明显透露着排序
两个字,自然较优的解是涉及到二分法
的。
假设我们用下图表示数组,水平线代表数字相同,横坐标代表数字下标。
我们发现除了最后水平的一段(黑色水平那段)之外,其余部分满足二分性质:竖直虚线左边的数满足
numbers[i] ≥ numbers[0];而竖直虚线右边的数不满足这个条件
。
我们要找的便是不满足上诉性质的那段中的最小值。
所以我们先将最后水平的一段删除,使得右半段不满足numbers[i] ≥ numbers[0]
,而是严格满足numbers[i] < numbers[0]
。
另外,不要忘记处理数组完全单调的特殊情况:
当我们删除最后水平的一段之后,如果剩下的最后一个数大于等于第一个数,则说明数组完全单调。
时间复杂度分析
二分的时间复杂度是$O(logn)$,删除最后水平一段的时间复杂度最坏是 $O(n)$,所以总时间复杂度是 $O(n)$。
参考y总的题解:https://www.acwing.com/solution/content/727/
class Solution {
public int minArray(int[] numbers) {
if(numbers == null || numbers.length == 0) return 0;
int left = 0;
int right = numbers.length-1;
//去除第二段有序数组中最后的和第一段第一个数相同的数
//使得第二段有序数组严格满足numbers[i] < numbers[0]。
while(right > 0 && numbers[right]==numbers[0]) right--;
//如果此时整个数组都有序了,那么numbers[0]就是最小值
if(numbers[0] < numbers[right]) return numbers[0];
while(left < right){
int mid = left + right >> 1;
if(numbers[mid] < numbers[0]){//说明mid落在了右半段,最小值在[left,mid]里面
right = mid;
}else{//最小值在[mid + 1,right]之间
left = mid + 1;
}
}
return numbers[left];
}
}
扩展题:LeetCode 33 搜索旋转排序数组
本人博客题解:LeetCode 33 搜索旋转排序数组
二分不是应该完全单调吗
// //去除第二段有序数组中最后的和第一段第一个数相同的数
// //使得第二段有序数组严格满足numbers[i] < numbers[0]。
// while(right > 0 && rotateArray[right]==rotateArray[0]) right–;
// //如果此时整个数组都有序了,那么numbers[0]就是最小值
// if(rotateArray[0] < rotateArray[right]) return rotateArray[0];
请问这段代码的意思是什么呀?
例如可能会出现:数组numders={4,5,6,4,4};去除和第一个元素相同的所有末尾元素。所以就有第二段有序数组:numders[i]<numders[0]。
{4,5,6,4,4} 变成 {4,5,6} 整个都是升序数组,出现这种情况numders[0]就是最小值