剑指 Offer 11. 旋转数组的最小数字
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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
思路1:遍历
题目的意思:输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
话说,管他是不是旋转数组,直接找出并返回数组的最小值即可。
也就是:找出数组的最小值
从前往后比较,遇到更小的替换,最后找出最小值
不难写出代码:
class Solution {
public int minArray(int[] numbers) {
//边界条件
if(numbers == null || numbers.length == 0) return 0;
int min = numbers[0];
for(int i = 0; i<numbers.length; i++){
if(numbers[i] < min)
{
min = numbers[i];
}
}
return min;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
思路2:遍历的优化
但是,这道题应该不是让我们这样解的,因为没有使用到原数组是排序好的数组这个信息。
假如,我们将输入的旋转后的数组再次改变为排序好的数组,那么,直接找出数组第一个元素就是最小值。
如何将输入后的旋转后的数组再次改变为排序好的数组呢?
可以从前往后找,找到一个前面的数字大于后一个数字的地方
可以从后往前找,找到一个前面的数字大于后一个数字的地方
其实,仔细想想,又没有让我们恢复排序数组,那么就没有必要对数组进行操作
那么,我们只需要找到一个前面的数字大于后一个数字的地方即可
从前往后找:
class Solution {
public int minArray(int[] numbers) {
//边界条件
if(numbers == null || numbers.length == 0) return 0;
//从前往后,找出第一个前面的数,大于后面的数的后面的数
int min = numbers[0];
for(int i = 0; i<numbers.length; i++){
if(numbers[i] < min)
{
min = numbers[i];
break;
}
}
return min;
}
}
比之前的程序,只是多了一个break
看了答案,解题是使用的二分查找
这倒也对,排序的数组,使用二分查找效率要高
但,问题是,这个数组也并不是排序的数组,而是排序数组的旋转
这样看来,这道题是考察二分查找的变种,如何在旋转后的排序数组中,使用二分查找找到最小值
思路3:二分查找
假设:
最小值所在的位置为i,值nums[i] = x;
那么,位置i之前的数据都小于x;位置i之后的数据都大于或等于x
为何前面是小于,后面是大于或等于呢?
比如[1,1,2,2,0,0]
那么,最小值的位置index可以是4,也可以是5,因为两个位置的值都是0
但,根据是排序的规则,我们可以默认第一个最小值为最小值,也就是index = 4的地方为最小值。
但是,最后求的是最小值,而不是位置,所以,index = 4或 index = 5都可以,反正最后返回的值都是0
其实,这里,你也可以规定i之前的数据都是小于或等于x,i之后的数据都是大于x。
根据上面的特征,可以得到nums[middle]的值,然后分情况讨论:
- 如果nums[middle] > nums[height],说明最小值在middle的右边
- 如果nums[middle] < nums[height],说明最小值在middle的左边
- 如果nums[middle] == nums[height]
那么,最小值不能确定在左边还是右边,
不能减少为minArray(numbers, low, middle);,有可能把最小值在右边的情况忽略掉
但是,可以使得height - 1,这是因为,height - 1最多是middle的地方,也没有把最小值忽略掉
根据以上思路,尝试写代码:
class Solution {
public int minArray(int[] numbers) {
//边界条件
if(numbers == null || numbers.length == 0) return 0;
return minArray(numbers, 0, numbers.length - 1);
}
//左闭右开
public int minArray(int[] numbers, int low, int height)
{
// int middle = (height - low)/2;
int middle = low + (height - low)/2;
// = low + height/2 - low/2
// = height/2 + low/2;
if(low < height){
if(numbers[middle] > numbers[height]){
return minArray(numbers, middle + 1, height);
}else if(numbers[middle] < numbers[height]){
return minArray(numbers, low, middle);
}else{
return minArray(numbers, low, --height);
}
}else{
return numbers[low];
}
}
}
需要注意的地方:
int middle = (height - low)/2;❌
int middle = low + (height - low)/2;✅
int middle = (height + low)/2;✅
–height,因为要用到height - 1的结果
为何:minArray(numbers, middle + 1, height);
为何:minArray(numbers, low, middle);
你们不觉得这很怪异吗?
为何前面的middle可以+1操作,而后面那个middle不可以做-1操作?
为何minArray(numbers, middle + 1, height);
能不能+1,是看+1后,会不会把最小值省略掉
如第一张图,nums[middle + 1] > nums[middle],所以不会省略最小值
即使,nums[middle + 1]正好就是最小值,nums[middle + 1] < nums[middle]
而求的是minArray(numbers, middle + 1, height);,依然不会省略掉最小值,所以,此处middle + 1是可以的
为何minArray(numbers, low, middle);
能不能middle - 1,是看-1后,会不会把最小值省略掉
如第二张图所示,nums[middle - 1] < nums[middle],不会省略最小值
问题是,如果nums[middle]此时正好就是最小值,那么,nums[middle - 1] > nums[middle],此时minArray(numbers, low, middle - 1);就会把最小值nums[middle]给漏掉,因此,middle-1是不可以的