《剑指 Offer》学习记录:题 11:旋转数组的最小数字

题 11:旋转数组的最小数字

题干

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

测试样例

样例一

普通测试点。
输入:

[3,4,5,1,2]

输出:

1

样例二

带有重复数字的数组的旋转。
输入:

[2,2,2,0,1]

输出:

0

样例三

最小值是数组的最后一个元素。

[2,2,2,0]

输出:

0

样例四

最小值是数组的第一个或最后一个元素。

[1,2,1]

输出:

1

样例五

数组旋转的元素个数是 0 个,也就是传了原始的数组进来。

[1,2,3]

输出:

1

样例六

数组中只出现过一个数字。

[1,1]

输出:

1

遍历查找

解题思路

这个做法没什么好说的,就是直接遍历一遍找到数组的最小值。虽然不能减少 O(n),但是可以减少 T(n)。观察上述的样例,显然当 nums[i] < nums[i-1] 时 nums[i] 就是数组的最小值。如果用这种方式找不到最小值,说明传入的数组的旋转元素个数为 0(对应样例五、六),这时应该返回第一个元素。

题解代码

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        for i in range(1, len(numbers)):
            if numbers[i] < numbers[i - 1]:
                return numbers[i]
        return numbers[0]

时空复杂度

最坏情况下需要把数组遍历一遍,所以时间复杂度为 O(n)。
由于只需要常数个数的辅助变量,因此空间复杂度为 O(1)。

二分查找

解题思路

这种方法是比较聪明的,观察下图所示的样例,发现所谓数组的旋转是把原本的数组分割成了 2 个递增的序列,且第一个序列的元素均不小于第二个序列的任何元素,而最小值是 2 个序列的界限。

此时的目的就变成了找到第 2 个递增序列的第一个元素,可以使用二分法来查找。定义 2 分查找的 3 个变量 high、mid、low,根据第一个序列的元素均不小于第二个序列的任何元素的特点,当 nums[mid] > nums[low] 时,说明待查找的元素在 mid 的右侧,需要执行 “high = mid + 1”;当 nums[mid] < nums[low] 时说明待查找的元素在 mid 的左侧,需要执行 “low = mid”。最终当 high >= low 时查找结束,直接返回 nums[high] 就行。
例如上面的样例,初始情况下 3 个变量 high、mid、low 的状态如下。此时由于 nums[mid] > nums[low] 说明待查找的元素在 mid 的右侧,需要执行 “high = mid + 1”。

第一轮二分以后 3 个变量 high、mid、low 的状态如下,此时由于 nums[mid] < nums[low] 说明待查找的元素在 mid 的左侧,需要执行 “low = mid”。

第二轮二分以后 3 个变量 high、mid、low 的状态如下,此时由于 nums[mid] < nums[low] 说明待查找的元素在 mid 的左侧,需要执行 “low = mid”。

第三轮二分以后 3 个变量 high、mid、low 的状态如下,此时由于不满足 high < low 的条件,二分查找结束返回 nums[high] 解决问题。

注意使用二分时可以能会遇到 nums[mid] = nums[low] 的情况,此时无法判断说明待查找的元素在数组的什么地方。遇到这种情况有 2 种解决方法,一种是直接使用遍历查找,另一种是用减治法令 low = low - 1 减小解空间的范围,其实换成遍历查找也是减治法。

题解代码

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        high = 0
        low = len(numbers) - 1

        while high < low:
            mid = int((high + low) / 2)
            if numbers[mid] > numbers[low]:
                high = mid + 1
            elif numbers[mid] < numbers[low]:
                low = mid
            else:
                low = low - 1
        return numbers[high]

时空复杂度

由于使用二分法一次可以将查找范围缩小一半,所以时间复杂度为 O(㏒n)。
由于只需要常数个数的辅助变量,因此空间复杂度为 O(1)。

参考资料

《剑指 Offer(第2版)》,何海涛 著,电子工业出版社
面试题11. 旋转数组的最小数字(二分法,清晰图解)

posted @ 2021-07-28 06:51  乌漆WhiteMoon  阅读(61)  评论(0编辑  收藏  举报