力扣 剑指offer11(旋转数组的最小数字)
题干:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
示例:
输入:numbers = [3,4,5,1,2] 输出:1
输入:numbers = [2,2,2,0,1] 输出:0
思路:
- 遍历查找,那每个人都会,那这题就失去它原有的意义了,其实这道题在剑指offer上的难度定级是hard,但是在力扣上被定为了简单题,会让很多人误以为这道题不重要
- 本题介绍二分查找思想对,查找效率进行提升
- 定义p1初始化为数组的0下标,p2为最后一个元素的下标
- 定义mid为(p1+p2)/2
- 我们的思想就是:每次一迭代,都能一次排除一半的元素,提高查找效率
- 因为旋转后,整个数组会被划分为两个子数组,前一部分的数组每个元素都应该比后面的第二个子数组的每个元素都应该单调不减
- 所以当p1标定到第一个子数组的最后一个元素下标处,而p2标定到第二个子数组的第一个元素下标处,此时的p2指向的元素就是所求.此时p2-p1=1
先分析最能体现思想的案例:[3, 4, 5, 1, 2]
由于numbers[mid]<numbers[p1]
所以mid处的元素应当属于第二个子数组
所以p2应当尽可能的标定到第二个子数组的更小的下标处,所以:p2=mid
此时:p2-p1=1,那此时p2已经标定到了第二个子数组的第一个元素的下标,所以返回numbers[p2]即为所求.
但是考虑上述最理想的情况还不够:
-
如果没有发生旋转的数组输入进去,画画图可以发现,最小值显然是numbers[0],但是mid处的元素比p1处的元素更大时,p1将标定到mid处,进而会跳过最小值numbers[0],就比如:[1, 3, 5],所以对此我们要上来就判断一下有没有发生旋转,怎么判断看后续代码
-
当mid处的元素和p1处的元素相等时,mid是既可以属于第一个子数组,也可以属于第二个子数组的!比如:[3, 4, 3, 3]此时mid处的3就属于第二个子数组,而
[3, 3, 3, 1, 2]此时mid处的元素就属于第一个子数组,且可以观察发现:当mid的值属于第二个子数组时,从mid到p2,应当都是同一个值!
代码:
class Solution {
public int minArray(int[] numbers) {
if(numbers==null||numbers.length==0) return 0;
int p1=0;
int p2=numbers.length-1;
while(p1<p2){
//没有旋转的话,不能写等号,写了等号如 1 3 1就不可行
if(numbers[p2]>numbers[p1]){
return numbers[0];
}
if(p2-p1==1){
return numbers[p2];
}
int mid=(p1+p2)/2;
//此处相当于考虑的是mid值和p1处的值相等,并且mid还属于第二个子数组的情况,而属于第一个子数组的情况在第25行中以包含
if(numbers[p1]==numbers[mid]&&numbers[mid]==numbers[p2]){
int min=numbers[p1];
for(int i=p1+1;i<p2;i++){
if(numbers[i]<min){
min=numbers[i];
}
}
return min;
}
if(numbers[mid]>=numbers[p1]){//如果mid和p1处的元素相等,并且mid属于第一个子数组,此时可以按照正常的p1尽可能大的标定即可
//此时是可以把保证mid位置处的元素是属于第一个子数组的
p1=mid;//此时也就是把最小值的范围排除了0-p1-1
}else{
p2=mid;
}
}
return numbers[0];
}
}