剑指Offer-11

剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例

1
2
3
4
5
输入:[3,4,5,1,2]
输出:1

输入:[2,2,2,0,1]
输出:0

解法:

暴力法:

1
2
3
4
5
6
7
8
class Solution:
def minArray(self, numbers: List[int]) -> int:
len_n = len(numbers)
cur_min = numbers[0]
for n in numbers[::-1]:
if cur_min > n:
cur_min = n
return cur_min

官方解法:二分查找

一个包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:

image-20210606155542091

其中横轴表示数组元素的下标,纵轴表示数组元素的值。图中标出了最小值的位置,是我们需要查找的目标。

我们考虑数组中的最后一个元素 $x$:在最小值右侧的元素,它们的值一定都小于等于$x$;而在最小值左侧的元素,它们的值一定都大于等于$x$。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

在二分查找的每一步中,左边界为 $low$,右边界为 $high$,区间的中点为 $pivot$,最小值就在该区间内。我们将中轴元素 $numbers[pivot]$ 与右边界元素 $numbers[highh]$ 进行比较,可能会有以下的三种情况:

  • 第一种情况是 $numbers[pivot] < numbers[high]$,这说明$numbers[pivot]$ 是最小值右侧的元素,因此我们可以忽略二分查找右半区间。
  • 第二种情况是 $numbers[pivot] > numbers[high]$,这说明 $numbers[pivot]$是最小值左侧的元素,因此我们可以忽略二分查找左半区间。
  • 第三种情况是 $numbers[pivot] == numbers[high]$,由于重复元素的存在,我们不能确定 $numbers[pivot]$ 是在最小值的左侧还是右侧,因此我们不能莽撞的忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论 $numbers[high]$ 是不是最小值,都有一个它的 [替代品],$numbers[pivot]$,因此我们可以忽略二分查找区间的右端点。

当二分查找结束时,我们就得到了最小值所在的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def minArray(self, numbers: List[int]) -> int:
low, high = 0, len(numbers) - 1
while low < high:
pivot = low + (high - low) // 2
# 忽略右区间
if numbers[pivot] < numbers[high]:
high = pivot
# 忽略左区间
elif numbers[pivot] > numbers[high]:
low = pivot + 1
else:
high -= 1
return numbers[low]
作者

bd160jbgm

发布于

2021-06-06

更新于

2021-06-06

许可协议