面试题11-旋转数组的最小数

题目:

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

解题思路

我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素大于或者等于后面子数组的元素。我们还注意到最小的元素刚好是这两个数组的分界线。在排序的数组中可以用二分查找实现O(logn)的查找。本题给出的数组在一定程度上是排序的,因此我们可以试着用二分查找法的思路来寻找这个最小的元素。

  1. 和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的原则,第一个元素应该是大于或者等于最后一个元素的;
  2. 接着我们可以找到数组中间的元素。如果中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时最小元素应该位于该中间元素之后,然后我们把第一个指针指向该中间元素,移动之后第一个指针仍然位于前面的递增子数组中;
  3. 同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时最小元素应该位于该中间元素之前,然后我们把第二个指针指向该中间元素,移动之后第二个指针仍然位于后面的递增子数组中;
  4. 按照上述思路,第一个指针总是指向前面递增数组****的元素,第二个指针总是指向后面递增数组的元素。最终它们会指向两个相邻的元素**,而第二个指针指向的刚好是最小的元素,这就是循环结束的条件。

在这里插入图片描述
特殊情况:
1. 如果把排序数组的0个元素搬到最后面,这仍然是旋转数组,我们的代码需要支持这种情况。如果发现数组中的一个数字小于最后一个数字,就可以直接返回第一个数字了。
2. 下面这种情况,即第一个指针指向的数字、第二个指针指向的数字和中间的数字三者相等,我们无法判断中间的数字1是位于前面的递增子数组还是后面的递增子数组。这样的话,我们只能进行顺序查找。时间复杂度为O(n)
在这里插入图片描述

C++实现

#include<iostream>
#include<vector>
using namespace std;
class Solution
{
public:
	int Min(vector<int> numbers)
	{
		int index1 = 0;
		int index2 = numbers.size()-1;
		int indexMid = index1;
		if (numbers.size() == 0)
			throw new std::exception("Invalid");
		while (numbers[index1]>=numbers[index2])
		{
			if (index1 == index2 - 1)
			{
				indexMid = index2;    //indexMid赋予新值,因为上一轮的值被释放掉了
				break;		
			}	
			int indexMid = (index1 + index2) / 2;
			if (numbers[index1] == numbers[index2] && numbers[index1] == numbers[indexMid])
			{
				return MinInOrder(numbers);
			}
			if (numbers[indexMid] >= numbers[index1])
				index1=indexMid ;
			else
				index2=indexMid ;
		}
		return numbers[indexMid]; //为了应对特殊情况,即把排序数组前面的0个元素搬到最后面
	}
	int MinInOrder(vector<int> numbers)
	{
		int result = numbers[0];
		for (int i = 1; i < numbers.size(); i++)
		{
			if (result > numbers[i])
			{
				result = numbers[i];
			}
		}
		return result;
	}

};

python实现

使用二分查找的方法,我们将中轴元素numbers[pivot]与右边界元素numbers[high]进行比较,可能有以下的三种情况:

  1. 第一种情况是numbers[pivot]<numbers[high],这说明numbers[pivot]是最小值右侧的元素,我们可以忽略右半区间;
  2. 第二种情况是numbers[pivot]>numbers[high],这说明numbers[pivot]是最小值左侧的元素,我们可以忽略左半区间;
  3. 第三种情况是numbers[pivot]=numbers[high],如下图所示,我们并不能确定numbers[pivot]究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论 numbers[high] 是不是最小值,都有一个它的「替代品」numbers[pivot],因此我们可以忽略二分查找区间的右端点。
class Solution:
    def minArray(self, numbers: List[int]) -> int:
        low, high = 0, len(numbers) - 1
        while low < high:
            pivot = low + (high - low) // 2
            if numbers[pivot] < numbers[high]:
                high = pivot 
            elif numbers[pivot] > numbers[high]:
                low = pivot + 1
            else:
                high -= 1
        return numbers[low]
  1. 时间复杂度:平均时间复杂度为 O(logn),其中 n 是数组numbers 的长度。如果数组是随机生成的,那么数组中包含相同元素的概率很低,在二分查找的过程中,大部分情况都会忽略一半的区间。而在最坏情况下,如果数组中的元素完全相同,那么while 循环就需要执行 n 次,每次忽略区间的右端点,时间复杂度为 O(n)。
  2. 空间复杂度:O(1)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值