剑指offer—面试题11:旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
最简单的方法就是遍历,直接遍历一次数组找到其中的最小值,时间复杂度O(n)。
算法一:
func minArray(_ numbers: [Int]) -> Int {
guard numbers.count > 0 else {
return -1
}
var min = numbers[0]
for num in 1..<numbers.count {
if min > numbers[num] {
min = numbers[num]
}
}
return min
}
算法一是一种暴力的解法,我们可以根据旋转数组的特性来优化,旋转后的数组实际上可以划分为两个排序好的子数组。而且前面的子数组元素都大于后面子数组的元素。而最小元素是这两个子数组的分界线。在排序的数组查找元素我们可以通过二分查找提供性能。该题给出的数组一定程度上也是排序的,所以也可以通过二分查找来寻找最小元素。
在二分查找的每一步中,左边界为low,右边界为high,区间的中点为 indexMid,最小值就在该区间内。我们将中轴元素 numbers[indexMid]
与右边界元素 numbers[high]
进行比较,可能会有以下的三种情况:
第一种情况是 numbers[indexMid]<numbers[high]
。这说明numbers[indexMid]
是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。
第二种情况是 numbers[indexMid]>numbers[high]
。这说明 numbers[indexMid]
是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。
第三种情况是 numbers[indexMid]==numbers[high]
。如下图所示,由于重复元素的存在,我们并不能确定numbers[indexMid]
究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论numbers[high]
是不是最小值,都有一个它的「替代品」numbers[indexMid]
,因此我们可以忽略二分查找区间的右端点。
算法二:
func minArray(_ numbers: [Int]) -> Int {
var low = 0
var high = numbers.count - 1
while low < high {
let indexMid = low + (high - low)/2
if numbers[indexMid] < numbers[high] {
high = indexMid
}else if numbers[indexMid] > numbers[high] {
low = indexMid + 1
}else {
high -= 1
}
}
return numbers[low]
}
时间复杂度:平均时间复杂度为 O(\log n)O(logn)