旋转数组的概念:
把一个数组最开始的若干个元素搬到数组的末尾,就是旋转数组。
例如: 数组{1,2,3,4,5}的一个旋转数组就是{3,4,5,1,2},把1,2放数组的后面。
题目:
- 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素
思路:
这题最直观的解答就是遍历一遍数组,直接找出其中最小的元素。时间复杂度是O(n)。但是没有利用旋转数组的特性。
我们可以再来看看旋转数组:
旋转数组的特性:
- 可以拆分为两个排好序的子数组
- 前面的子数组的元素大于或者等于后面子数组的元素。
- 最小的数组恰好是这两个数组的分界线。
举例说明:
旋转数组在一定程度上是排序的,因此试试用二分法找最小的元素。
直接举例说明,
我们以数组{3,4,5,1,2},指定两个指针,一个指向第一个元素3,一个指向最后一个元素2,如下图所示。位于两个指针中间的元素是5,它大于第一个指针指向的元素。因此中间数字5一定是前面的递增子数组中的元素,故最小的元素一定位于它的后面。所以移动第一个指针指向中间,如图所示,缩小查询范围。
这时,位于两个指针中间的元素是1,1小于第一个指针指向的元素,所以数字1位于后面的递增子数组中,故最小元素在数字1的前面或者就是数字1,这时将第二个指针指向数字1,缩小查询范围,如图所示。
此时,两个指针的距离是1,所欲第一个指针指向的是前面的递增子数组的最后一个元素,即最大的元素;第二个指针指向的是后面的递增子数组中第一个元素,即最小的元素,这就找到了。
代码(继续看下去)
def MinInOrder(array):
if array == None or len(array) <= 0: # 排除空集
return false
l = 0 # 定义左指针
r = len(array) - 1 # 定义右指针
while(array[l] >= array[r]):
if r - l == 1: # 当左右指针相邻时,说明找到了,右指针指向的就是最小的元素
return array[r]
break
mid = l + ((r - l) >> 1) # mid是中间元素的index
if array[mid] > array[l]: # 若中间元素大于左指针指向的元素,说明最小元素在其后,故缩小查询范围
l = mid
else: # 反之同理
r = mid
return array[mid]
输入:
array = [3,4,5,1,2]
MinInOrder(array)
输出:1
别急,还没有完,其实还有两个问题没考虑到
问题一:
前面说旋转数组是把前面部分元素搬到后面去,这没错,但是有个特殊情况,那就是我把前面0个元素搬到后面去,即旋转数组还是自己本身,哈哈哈,是不是有意思了。
这个时候,数组中最小的元素就是第一个元素了,我们试着运行上的代码:
输入:
array = [1,2,3,4,5]
MinInOrder(array)
输出:5
看见没有。输出的是5,最大的元素找出来,但我们要的是最小的元素,所以我们得改改代码
问题二:
看个例子,数组{0,1,1,1,1} 现在有两个旋转数组 {1,0,1,1,1} 和数组 {1,1,1,0,1}
看上图,会发现,左右指针指向的元素和中间元素是一样,这个时候,就不知道中间元素到底是前面的递增子数组还是后面的递增子数组,所以无法缩小查找范围。这时我们就得采用顺序查找的方式了。看完整的代码:
class Solution:
def Min(self,array):
if array == None or len(array) <= 0: # 排除空集
return False
l = 0 # 定义左指针
r = len(array) - 1 # 定义右指针
mid = 0 # 将指针mid初始为0,一旦发现数组第一个元素小于最后一个元素,即数组是排序的(问题一),这直接返回
while(array[l] >= array[r]):
if r - l == 1: # 当左右指针相邻时,说明找到了,右指针指向的就是最小的元素
return array[r]
break
mid = l + ((r - l) >> 1) # mid是中间元素的index
# 如果左指针、右指针指向的元素和中间元素一致时(问题二),采用顺序查找
if array[l] == array[mid] == array[r]:
return self.MinOrder(array,l,r)
if array[mid] > array[l]: # 若中间元素大于左指针指向的元素,说明最小元素在其后,故缩小查询范围
l = mid
else: # 反之同理
r = mid
# 如果左指针、右指针指向的元素和中间元素一样,则只能顺序查找
return array[mid]
def MinOrder(self,array,l,r): # 顺序查找,循环遍历所有元素,找最小的元素
re = array[l]
for i in range(l+1,r):
if re > array[i]:
re = array[i]
return re
if __name__ == "__main__":
# 验证
# 功能测试:输入数组升序数组的一个旋转数组,数组中有重复或在没有重复的数字
test1 = [3,4,5,1,2]
test2 = [1,0,1,1,1]
# 边界测试:输入数组只有一个元素
test3 = [5]
# 特在测试:输入数组是空
test4 = []
solution = Solution()
print("test_1:", solution.Min(test1))
print("test_2:", solution.Min(test2))
print("test_3:", solution.Min(test3))
print("test_4:", solution.Min(test4))
输出:
test_1: 1
test_2: 0
test_3: 5
test_4: None
简写
def minNumberInRotateArray(rotateArray):
# write code here
# 二分查找:找左右的方法:右边的值大于中指时,说明最小值在左边,反之
if rotateArray == None: # 排除空集
return 0
left = 0 # 定义左指针
right = len(rotateArray) -1 # 定义右指针
while left <= right:
mid = (right + left) >> 1 # 定义中间指针
if rotateArray[mid] < rotateArray[mid-1]: # 若中间指针对应的元素恰好小于前一个元素,说明当前元素就是最小的元素
return rotateArray[mid]
elif rotateArray[mid] < rotateArray[right]: # 若中间元素小于最右边的元素,说明最小值在左边,更新指针
right = mid-1
else: # 反之
left = mid+1
return 0