题目来源:牛客网-剑指Offer专题
题目地址:旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
题目解析
方法一:
最直观的方法莫过于漠视题目给的条件“非递减排序”,直接遍历整个数组找到里面最小的元素。就算我很菜,也不忍下手写这样的代码。
于是,对暴力法稍加优化才有了第一种算法:以第一个元素 为基准找到第一个比它小的元素,这个就是原数组的第一个元素,即最小值。找不到则返回第一个元素,说明没有元素参与旋转。
虽然做了优化,在极端情况下还是要遍历整个数组,时间复杂度 。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
//特判
if (array.length == 0) {
return 0;
}
//以第一个元素为基准
int flag = array[0];
for (int i = 1; i < array.length; i++) {
//找到第一个比它小的元素,即为答案
if (flag > array[i]) {
return array[i];
}
}
return flag;
}
}
方法二:
通常在有序的序列中查找某个值的问题都可以用二分查找,本题虽然不是整个序列又是有序的,但是它也算是局部有序,也可以用上二分查找。
经过观察我们可以发现,旋转数组可以分成左右两个部分,而我们要找的数就在分界线的右边。算法描述如下:
- 首先,我们设定左右指针 和 ,然后通过判断中间指针 指向的数是否比 指向的数小。
- 如果是,则说明 位于数组右边部分,于是将 指针移动到该位置上;否则,说明 位于数组左边部分,于是将指针移动到该位置上。
- 最后不断重复上诉过程,直到找到数组两个部分的边界所在,即 。这时, 就是我们要找的最小值。
二分查找的过程如上图所示 ,算法时间复杂度为 。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if (array.length == 0) {
return 0;
}
int left = 0, right = array.length - 1;
while (array[left] >= array[right]) {
int mid = (left + right) / 2;
//找到边界所在
if (right - left == 1) break;
if (array[mid] < array[right]) {
right= mid;
} else {
left= mid;
}
}
return array[right];
}
}
虽然这样在牛客网中通过了,但是我发现了一组可以hack这份代码的数据:
正确答案:
代码返回:
方法三:
有问题就肯定会有对策,这里借用牛客网大佬FINACK的思路。
分下面三种情况进行讨论:
- array[mid] > array[right]时,
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
left = mid + 1 - array[mid] == array[right]时,
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边还是右边,这时只好逐个尝试。
right = right - 1 - array[mid] < array[right]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。
right = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字,比如 array = [4,6],此时array[left] = 4 ;array[mid] = 4;array[right] = 6;如果right = mid - 1,就会产生错误, 因此right = mid。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if (array.length == 0) {
return 0;
}
int left = 0, right = array.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (array[mid] > array[right]) {
left = mid + 1;
} else if (array[mid] == array[right]) {
right--;
} else {
right = mid;
}
}
return array[left];
}
}
只是可惜,在所有数字都一样的情况下,算法的时间复杂度会退化成 。当然,这是没有办法的事情。
如果本文对你有所帮助,要记得点赞哦~