剑指offer之面试题11:旋转数组的最小数字

旋转数组的最小数字

1、题目

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

输入参数:一维数组numbers,数组长度length

输出参数:最小元素的值,或者抛出 “传入参数错误” 的异常

2、解题

解这道题的关键在于搞明白数组的最小元素在旋转数组中的位置

旋转数组可以被划分为两个子数组,而且前面子数组的元素都大于等于后面子数组的元素,最小元素的位置即为两个子数组的分界处,因此我们可以通过二分查找法的思路来寻找最小元素。

具体步骤如下:

  • 用两个指针 p1、p2 分别指向数组的第一个元素和最后一个元素

  • 用指针 indexMid 指向数组的中间元素。

  • 将 indexMid 指向的数组元素 numbers[pMid] 和 p1指向的数组元素 numbers[p1] 比较

    • 若 numbers[pMid] >= numbers[p1],说明最小元素在numbers[pMid]的后面
    • 反之,则numbers[pMid] <= numbers[p2],说明最小元素在numbers[pMid]的前面
    • 反之,则说明最小元素在后面的子数组(即numbers[indexMid] )
  • 按照最小元素的位置,更新 p1 或 p2 的值,也就是缩小最小元素所在数组的范围

  • 循环使用二分法,缩小范围。当 p1 指向前面子数组的末尾,p2 指向后面子数组的开头时,循环结束,此时 p2 所指即为最小元素。

以数组{3, 4, 5, 1, 2}为例,先将 p1 指向第0个元素,p2 指向第四个元素,下面用图代表指针的移动过程。其中空白矩形表示前面的递增子数组,阴影表示后面的递增子数组

J69bnA.png

numbers[p1] <= numbers[pMid],指针p1后移

J6C7CT.png

numbers[p2] >= numbers[pMid],指针p2前移

J6PmIP.png

此时指针 p1与指针p2相邻,最小元素找到,即为 p2所指元素

但若是就此而编写代码,则存在这样一个问题,当 numbers[p1] 、numbers[p2]、numbers[pMid] 这三个数相同时,按常理我们将 pMid 赋值给 p1,认为最小元素在 numbers[pMid] 的后面,可是情况却并非如此。

以数组{0, 1, 1, 1, 1}为例,其旋转数组可以为{1, 0, 1, 1, 1}或{1, 1, 1, 0, 1},如下图所示:

J6PaGT.png
J6PQxg.png

此时,numbers[p1] = numbers[pMid] = numbers[p2],我们无法确定中间的数字1是属于第一个递增子数组还是第二个递增子数组。这时我们只能采用普通的顺序查找方法,时间复杂度变为O(n)。

3、代码
int Min(int* numbers, int length) {
    //鲁棒性检查
    if (numbers == nullptr || length <= 0)
        throw new std::exception("Invalid parameters!");
	//指针初始化
    int p1 = 0;
    int p2 = length - 1;
    int pMid = p1;
    //二分法开始
    while (numbers[p1] >= numbers[p2]) {
        //若两指针相邻,找到最小元素
        if (p2 - p1 == 1) {
            pMid = p2;
            break;
        }
        //pMid指针获得指向
        pMid = (p1 + p2) / 2;
		//若三个指针所指元素相等,采用顺序查找
        if (numbers[p1] == numbers[p2] && numbers[p1] == numbers[pMid])
            return MinInOrder(numbers, p1, p2);
		//若pMid所指元素大于p1所指元素,p1后移,反之p2前移
        if (numbers[pMid] >= numbers[p1])
            p1 = pMid;
        else if (numbers[pMid] <= numbers[p2])
            p2 = pMid;
    }
	//最后返回最小元素
    return numbers[pMid];
}

int MinInOrder(int* numbers, int p1, int p2) {
    int result = numbers[p1];
    for (int i = p1; i <= p2; i++)
        if (result > numbers[i])
            result = numbers[i];
    return result;
}
4、注意点
  • 鲁棒性的检查,参数出错时抛出异常而不是返回某个值
  • 在最开始给 pMid 赋值为 p1,是因为如果数组是全部排序而未旋转的,可以直接返回数组的第一个数字
  • 注意 p1、p2 在与 pMid 比较过程中的指向变化

数出错时抛出异常而不是返回某个值

  • 在最开始给 pMid 赋值为 p1,是因为如果数组是全部排序而未旋转的,可以直接返回数组的第一个数字
  • 注意 p1、p2 在与 pMid 比较过程中的指向变化

测试代码见:https://github.com/zhedahht/CodingInterviewChinese2/blob/master/11_MinNumberInRotatedArray/MinNumberInRotatedArray.cpp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值