一.题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
二.题目分析
题目在原先已经排序的数组基础上做了旋转操作,将原先排在数组前面的数字旋转到了数组的后面。这些被旋转到数组末尾的数字之前是排在数组的前面且是非递减的。因此旋转到末尾的部分也是非递减排列的且这些数字均不大于没有旋转的数字部分。所以旋转之后的整个数组由两部分组成,一部分是原先排在最前面被旋转到末尾的数字,一部分没有被旋转的数字。这两部分内部都是非递减排列的,且前一部分的数字都大于等于后一部分的数字。
解决思路:
可以仿照二分查找的思想,先获取到中间位置的数字。它要么落在左半区间,要么落在右半区间。
设left,mid,right 分为待查找区间的左端点、中点、右端点的索引值。
(1)若arr[left] > arr[mid]
中间数字的值小于左端点的值,由于左区间是非递减排列的,因此可以断定mid落在了右半区间。而我们要查找的数组的最小值,也即是右半区间的第一个元素。所以我们可以将待查找区间缩小为[left,mid],最小值一定在这个区间内。
(2)反之,若arr[left] <= arr[mid]
此时mid大概率落在了左半区间,但是也有可能落在右半区间(此时右半区间的元素都相等且等于左半区间的第一个元素)。若落在右半区间则其实当前位置以及之后的所有元素都是最小值,因此将区间缩小为[left,mid]或[mid,right]均可。但是若mid是落在左半区间,则需要将待查找区间更新为[mid.right],最小值一定位于这个区间内。所以arr[left]<=arr[right]时,需要将待查找区间更新为[mid,right]。
int minNumberInRotateArray(vector<int> rotateArray) {
if (rotateArray.size() == 0)
return 0;
int left = 0, right = rotateArray.size() - 1;
int mid = 0;
while (rotateArray[left] >= rotateArray[right]) {
mid = (left + right) / 2;
if (left == right - 1)
return rotateArray[right];
if (rotateArray[mid] == rotateArray[left]
&& rotateArray[mid] == rotateArray[right]) {
// 无法判断最小值位于左半区间还是右半区间
int ret = rotateArray[left];
for (int i = left + 1; i < right; i++) {
if (ret > rotateArray[i])
ret = rotateArray[i];
}
return ret;
} else if (rotateArray[mid] < rotateArray[left])
right = mid; // mid落在右半区间,最小值为于[left, mid]
else
left = mid; // mid 落在左半区间,最小值位于[mid,right] }
}
return rotateArray[mid];
}
由于数组不是严格递增的,因此可能出现相同元素的情况。若left,mid,right这三个位置的元素相等。则没有办法进一步缩小待查找区间,或者当前区间只有一个元素。所以直接遍历当前查找区间,找出最小值即可。
代码中循环的条件是arr[left]>=arr[right],表示此时的待查找区间是被旋转过的。如果arr[left]<arr[right],则表示当前整个区间是非递减的,只需要返回第一个元素即可。若初始的区间是非递减的,则直接返回arr[0]。若是缩小之后的某个区间是非递减的,mid一定定位到了最小值的位置。所以循环结束之后返回arr[mid]即可。
前面已经判断了查找区间只有一个元素的情况。若当前区间只有两个元素,由于循环条件保证了区间内的元素是旋转过的,所以第二个元素即是最小值。
也可以用mid,right处的元素的大小关系来判断mid落在哪个半区间。若arr[mid]>arr[right],则mid落在左半区间。反之,mid落在右半区间。
int minNumberInRotateArray(vector<int> rotateArray) {
if (rotateArray.size() == 0)
return 0;
int left = 0, right = rotateArray.size() - 1;
int mid = 0;
while (rotateArray[left] >= rotateArray[right]) {
mid = (left + right) / 2;
if (left == right - 1)
return rotateArray[right];
if (rotateArray[mid] == rotateArray[left]
&& rotateArray[mid] == rotateArray[right]) {
// 无法判断最小值位于左半区间还是右半区间
int ret = rotateArray[left];
for (int i = left + 1; i < right; i++) {
if (ret > rotateArray[i])
ret = rotateArray[i];
}
return ret;
} else if (rotateArray[mid] <= rotateArray[right])
right = mid; // mid落在右半区间,最小值为于[left, mid]
else
left = mid; // mid 落在左半区间,最小值位于[mid,right] }
}
return rotateArray[mid];
}