旋转数组的最小数字
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 指向第四个元素,下面用图代表指针的移动过程。其中空白矩形表示前面的递增子数组,阴影表示后面的递增子数组
numbers[p1] <= numbers[pMid],指针p1后移
numbers[p2] >= numbers[pMid],指针p2前移
此时指针 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},如下图所示:
此时,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 比较过程中的指向变化