4.3python如何找出旋转数组的最小元素

题目描述:

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

思路:
python中可以使用列表来表示有序数组。
这个问题的一般化描述:
有一个数组 X[0,…,n-1],现在把它分为两个子数组:X1[0,…,m],X2[m+1,…,n-1],交换这个两个数组在大数组 X 中的顺序,使数组 X 由 X1X2 变为 X2X1。
对于本问题,最容易想到且最简单的方法是遍历,但是没有用到题目说到的有序这一特点。
介绍二分查找法。
通过数组的特性可以发现,数组元素首先是递增的,然后突然下降到最小值,然后递增。虽然如此,但是还有三种情况需要注意:
(1) 数组本身是没有发生过旋转的,是一个有序的数组,例如序列[1,2,3,4,5,6]。
(2) 数组中元素值全部相等,例如序列[1,1,1,1,1,1]。
(3) 数组中元素值大部分都相等,例如序列[1,0,1,1,1,1]。
通过旋转数组的定义可知,经过旋转之后的数组实际上可以划分为两个有序的子数组,前面的子数组的元素都大于或等于后面子数组的元素值。可以根据数组元素的这个特点,采用二分查找的思想不断缩小查找范围,具体实现
按照二分查找的思想,给定数组arr,首先定义两个变量 low 和 high,分别数组第一个元素和最后一个元素的下标。按照题目中对旋转规则的定义,(旋转后)第一个元素应该是大于或等于最后一个元素的(当旋转个数为0,即没有旋转的时候,要单独处理,直接返回数组第一个元素)。接着遍历数组中间的元素 arr[mid],其中 mid=(low+high)/2。 ([3,4,5,1,2])
(1) 如果 arr[mid]<arr[mid-1],则 arr[mid] 一定是最小值;
(2) 如果 arr[mid+1]<arr[mid], 则 arr[mid+1]一定是最小值;
(3) 如果 arr[high]>arr[mid],则最小值一定在数组左半部分;
(4) 如果 arr[mid]>arr[low],则最小值一定在数组的右半部分;
(5) 如果 arr[low] ==arr[mid] 且 arr[high]==arr[mid],则此时无法区分最小值是在数组的左半部分还是右半部分,(例如[2,2,2,2,1,2],[2,1,2,2,2,2])。在这种情况下,只能分别在数组的左右两部分找最小值 minL 和 minR,最后求出两者的最小值。

代码实现:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2020/1/27 16:09
# @Author  : buu
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/weixin_44321080
def getMin1(arr, low, high):
    """
    [1,2,3,4,5]-->[3,4,5,1,2]
    用列表表示有序数组
    :param arr: 旋转后的有序数组
    :param low: 数组第一个元素的下标
    :param high: 数组最后一个元素的下标
    :return:
    """
    # 如果旋转个数为0,即没有旋转,单独处理,直接返回数组头元素
    if high < low:
        return arr[0]
    # mid=(low+high)//2,下述写法是为了防止溢出
    mid = low + ((high - low) >> 1)
    # 判断arr[mid]是否为最小值
    if arr[mid] < arr[mid - 1]:
        return arr[mid]
    # 判断arr[mid+1]是否为最小值
    elif arr[mid + 1] < arr[mid]:
        return arr[mid + 1]
    # 最小值一定在数组的左半部分
    elif arr[high] > arr[mid]:
        return getMin(arr, low, mid - 1)
    # 最小值一定在数组的右半部分
    elif arr[mid] > arr[low]:
        return getMin(arr, mid + 1, high)
    else:  # arr[low]==arr[mid]&&arr[mid]==arr[high]
        return min(getMin(arr, low, mid - 1), getMin(arr, mid + 1, low))


def getMin(arr):
    if arr == None:
        print('参数不合法!')
        return
    else:
        return getMin1(arr, 0, len(arr) - 1)


if __name__ == '__main__':
    array1 = [5, 6, 1, 2, 3, 4]
    array2 = [1, 1, 0, 1]
    mins = getMin(array1)
    print('1: ', mins)
    mins = getMin(array2)
    print('2: ', mins)

结果:
在这里插入图片描述
算法性能分析:
一般而言,二分查找的时间复杂度为O(logN) (以2为底),只有每次都满足第 (5)条的时候才需要对数组中所有元素都进行遍历,此时时间复杂度为O(n)。

引申:

如何实现旋转数组功能?

思路:
先分别把两个子数组的内容交换(各自逆序),然后把整个数组的内容交换(将大数组内容逆序)。
在这里插入图片描述
代码实现:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2020/1/27 16:40
# @Author  : buu
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/weixin_44321080
def swap(arr, low, high):
    """
    交换数组 low 到 high 的内容
    不同下标(low++,high--)的元素两两交换
    :param arr:数组
    :param low:数组下界
    :param high:数组上界
    :return:
    """
    while low < high:
        tmp = arr[low]
        arr[low] = arr[high]
        arr[high] = tmp
        low += 1
        high -= 1


def rotateArr(arr, div):
    """
    数组向右移动 div 个元素
    :param arr: 数组
    :param div: 
    :return: 
    """
    if arr == None or div < 0 or div >= len(arr):
        print('参数不合法!')
        return
    if div == 0 or div == len(arr) - 1:  # 不需要旋转
        return
    swap(arr, 0, div)  # 交换第一个数组内容
    swap(arr, div + 1, len(arr) - 1)  # 交换第二个数组内容
    swap(arr, 0, len(arr) - 1)  # 交换整个数组内容


if __name__ == '__main__':
    arr = [1, 2, 3, 4, 5]
    rotateArr(arr, 2)
    i = 0
    while i < len(arr):
        print(arr[i], end=' ')
        i += 1

结果:
在这里插入图片描述
算法性能分析:
需要遍历两次数组,时间复杂度为O(n);
交换两个变量的值,需要使用一个辅助存储空间,所以空间复杂度为O(1)。

end

发布了76 篇原创文章 · 获赞 2 · 访问量 2548

猜你喜欢

转载自blog.csdn.net/weixin_44321080/article/details/104093187
今日推荐