旋转数组的最小数字

         把一个有序递增数组的最开始的若干个元素 搬到数组的末尾,形成的新数组,我们称之为旋转数组。求旋转数组的最小数字,即输入一个递增排序数组的一个旋转数组,输出旋转数组中的最小数字。例如,输入数组{3,4,5,1,2},输出1。{3,4,5,1,2}为{1,2,3,4,5}的旋转数组。

         思路:最简单的做法就是不管旋转数组的特性,直接遍历一遍数组,找出其中最小的数字即可,时间复杂度为O(n)。但是,如果利用上旋转数组的特性之后,时间复杂度就会降为O(logn)。因为旋转数组实际上可以划分为两个排序的子数组,而且前面子数组的元素都大于或者等于后面子数组的元素,而且数组中最小的元素刚好是这两个子数组的分界线,于是我们便可以借用二分查找法的思路来解决。

         我们用两个指针分别指向数组的第一个元素和最后一个元素。根据旋转数组特性可知,第一个元素大于或等于最后一个元素。然后我们找到最中间的元素,如果中间元素位于第一个子数组,那么它应该大于或者等于第一个指针指向的元素,此时数组中最小的元素应该位于该中间元素的后面,然后我们就可以把第一个指针指向中间元素,缩小查找范围。移动之后的第一个指针仍然位于第一个递增子数组中。如果中间元素位于第二个子数组,那么它应该小于或等于第二个指针指向的元素,此时数组中最小的元素应该位于该中间元素的前面,然后我们就可以把第二个指针指向中间元素,缩小查找范围。移动之后的第二个指针仍然位于第二个递增子数组中。最终的结果就是,第一个指针指向第一个子数组的最后一个元素,第二个指针指向第二个子数组的第一个元素,即两个指针指向两个相邻的元素,而第二个指针指向的元素就是最小的。

         以{3,4,5,1,2}为例,P1指向3,P2指向2,此时中间元素的下标为2,值Mid = 5,它大于P1指向的数字,说明它位于第一个子数组中,而且最小的数字一定位于它的后面,因此我们使P1指向5。此时中间元素Mid = 1,它小于P1指向的元素,说明它位于第二个子数组中,而且最小的数字应该在它前面或者就是它自己。因此我们移动第二个指针P2,指向1,此时P1与P2相邻,结束,P2所指即是最小元素。

#include <stdio.h>
int FindMin(int a[],int len)
{
    if (len <= 0)
    {
	return -1;
    }
    else
    {
	int i = 0;
	int j = len - 1;
	int middle = 0;
	while (a[i] >= a[j])
	{
            if (j - i == 1)
            {
		middle = j;
		break;
	    }
	    else
	    {
		middle = (i + j) / 2;
		if (a[middle] >= a[i])
		{
		    i = middle;
		}
		else if (a[middle] <= a[j])
		{
		    j = middle;
		}
	    }
	}
	return a[middle];
    }
}
int main()
{
    int a[] = { 3, 4, 5, 6, 7 };
    int len = sizeof(a) / sizeof(a[0]);
    int min = FindMin(a,len);
    printf("数组中最小的数为:%d\n", min);
    return 0;
}

 

         上面的代码也可以很好的解决当把数组的前0个元素放到数组末尾的特殊情况。但是还有一种情况容易被忽略,就是当P1和P2指向的两个元素相同时。在上面的代码中,当P1和P 2指向的元素相等时,我们把Mid赋值给了P1,也就是我们认为最小的元素是在Mid的后面,但是,实际情况并不是这样,当数组为{1,0,1,1,1}时,正确答案是0,但是上面的代码运行结果却是1。

         针对于两个特殊的用例,{1,0,1,1,1}和{1,1,1,0,1},我们可以看到,P1和P2指向的元素相等,都是1,中间元素Mid也是1。此时,对于特殊用例1来说,中间元素位于第二个子数组中,而对于特殊用例2来讲,中间元素位于第一个子数组中。因此当两个指针指向的数字和它们中间的数字相等时,我们无法判断中间的数字位于哪个子数组中,也就无法移动指针来缩小查找范围了,此时就只能顺序查找了。

#include <stdio.h>
int FindMinInorder(int a[], int len)
{
    int result = a[0];
    for (int i = 1; i < len; i++)
    {
	if (result > a[i])
	{
	    result = a[i];
	}
    }
    return result;
}
int FindMin(int a[],int len)
{
    if (len <= 0)
    {
	return -1;
    }
    else
    {
	int i = 0;
	int j = len - 1;
	int middle = 0;
	while (a[i] >= a[j])
	{
	    if (j - i == 1)
	    {
		middle = j;
		break;
            }
	    middle = (i + j) / 2;
	    if (a[middle] == a[i] && a[i] == a[j])
	    {
		return FindMinInorder(a,len);
	    }
	    if (a[middle] >= a[i])
	    {
	        i = middle;
	    }
	    else if (a[middle] <= a[j])
	    {
		j = middle;
	    }
	}
	return a[middle];
    }
}
int main()
{
    int a[] = { 1,0,1,1,1};
    int len = sizeof(a) / sizeof(a[0]);
    int min = FindMin(a,len);
    printf("数组中最小的数为:%d\n", min);
    return 0;
}

         此时,解题完毕!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值