Leetcode刷题:剑指offer【面试题11 旋转数组的最小数字】

文章目录

  • 方法 1
  • 方法 2

【面试题11 旋转数组的最小数字】

难度: 简单

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

Leetcode题目对应位置: 面试题11:旋转数组的最小数字

最直观的思路就是从头到尾遍历数组,但是时间复杂度为 O(n),且没有利用好旋转数组的特性。

方法 1

只要是题目要求在 排序或部分排序 的数组中查找一个数字或者统计某个数字出现的次数,都可以尝试用二分查找。本题中最小元素恰好是两个有序子数组的分界线。

用两个指针分别指向数组的头 low 和尾 high,二分取 mid,然后判断 mid 指向的元素和 high 指向的元素的大小关系:

1)若 numbers[mid] >= numbers[low],那么 mid 在左排序数组中,因为右排序数组的任意一个数都小于等于左排序数组的数,令 low = mid
2)若 numbers[mid] <= numbers[high],那么 mid 在右排序数组中,令 high = mid

循环执行到两个指针分别指向左排序数组的最后一个元素和右排序数组的第一个元素,此时 high 就是最小的目标元素。

3)若 numbers[low] == numbers[mid] == numbers[high],此时无法判断 mid 在左还是右,考虑顺序查找。

需要处理的特殊情况:

1)如果将数组前的 0 个元素旋转到后面,实际上仍然是原数组本身,最小值就是第一个元素;
2)如果数组只有 1 个元素,则最小值就是该元素;
3)当 mid、low、high 所指向的元素都相同时,无法确定最小值是在 mid 之前还是之后,此时可以转而选用顺序查找。

时间复杂度:O(log2N),顺序查找时为 O(n)
空间复杂度:O(1)

题简单,但实际做起来还是有点繁琐,Python 代码:

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        if not numbers: return
        low = 0
        high = len(numbers) - 1
        # 未旋转
        if numbers[low] < numbers[high]:
            return numbers[low]
        # 仅有一个数字
        if low == high:
            return numbers[low]

        while numbers[low] >= numbers[high]:
            if high - low == 1: 
                mid = high
                break
            else: mid = int((high - low) / 2) + low
			
			# 顺序查找
            if (numbers[low] == numbers[mid] and numbers[mid] == numbers[high]):
                length = len(numbers)
                result = numbers[0]
                for i in range(1, length):
                    if numbers[i] < result:
                        result = numbers[i]
                        break
                return result

            else:
                if numbers[mid] >= numbers[low]:  # mid位于左排序数组
                    low = mid
                elif numbers[mid] <= numbers[high]: # mid位于右排序数组
                    high = mid
        return numbers[mid]
            

在这里插入图片描述

方法 2

感觉自己写的代码不够优美,学习一下大佬们的做法:

仍然是用两个指针分别指向数组的头 low 和尾 high,二分取 mid,然后判断 mid 指向的元素和 high 指向的元素的大小关系:

1)若 numbers[m] > numbers[high],m 就一定在左排序数组;
2)若 numbers[m] < numbers[high],m 就一定在右排序数组;
3)若 numbers[m] == numbers[high],无法判断,则执行 high = high - 1 缩小判断范围。

和思路1的区别在于,这里第一种情况更换为判断 numbers[m] > numbers[high]。虽然 numbers[m] > numbers[low] 也能判断出 mid 在左排序数组,但是像这种特例 [1, 2, 3, 4, 5],即没有旋转的情况(相当于在 0 位置进行旋转,整个数组作为右排序数组),按照 numbers[m] > numbers[low] 的判断结果,mid 是在左排序数组(此时算法会不断向后找目标值),但实际上 mid 应该属于右排序数组(应往前找目标值)。

另一点,当无法判断 mid在左或右时,可以令 high 不断自减 1 缩小查找范围,因为 high 和 mid 所指元素相等,根据数组有序的性质,mid 和 high 之间的元素都与它们相等,那么 high - 1 只是在抛弃重复值,并不会影响结果。另外有可能会出现抛弃掉最小值的情况,但此时在 low、high、mid 值相同的前提下,仍然能继续向前查找到最小值。

时间复杂度:O(log2N),特殊情况下退化到 O(n),比如 [1, 1, 1, 1]
空间复杂度:O(1)

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]: i = m + 1
            elif numbers[m] < numbers[j]: j = m
            else: j -= 1
        return numbers[i]

代码来源:mian-shi-ti-11-xuan-zhuan-shu-zu-de-zui-xiao-shu-3

我 jio 得这一点儿也不像简单题,这是我提交后解答错误最多的一道题。


参考资料:
[1] 面试题11:旋转数组的最小数字
[2] 剑指 offer 第二版
[3] LeetCode 面试题11 题解 作者:Krahets

你可能感兴趣的:(今天刷题了吗)