题目描述:
把一个有序数组最开始的若干个元素搬到数组的末尾,称之为数组的旋转。
输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。例如数组 [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