题目剖析:
按照书上的解法:
旋转后的数组可以分为两个有序子数组,不考虑重复数字的情况下,前面的数组都要大于后面的数组。这是找的就是后面数组中的第一个数字。如果有重复的数字,只能顺序查找了。
思路: (双指针解法,二分法)
- 直接用一个指针指向第一个元素,一个指针指向最后一个元素。没有重复元素的话,第一个元素是大于最后的元素的。
- 在让mid = (left + right )/ 2 的元素,若中间的元素大于第一个元素,则中间的元素在前面递增子数组中,这时就可让left指针指向中间元素
- 若中间的元素小鱼第一个元素,则中间的元素位于后面的递增数组中,这时就可以让right指针指向中间的元素。
- 这样一直查找,最后找到的最小元素就是第二的子数组的第一个元素。
牛客网AC代码如下
class Solution {
public:
int MinInOrder(vector<int> rotateArray, int left, int right) {
int re = rotateArray[left];
for (int i = left + 1; i <= right; ++i) {
if (re > rotateArray[i]) re = rotateArray[i];
}
return re;
}
// 找中位数2版本 (牛客网可AC,1, 0, 1, 1, 1 测试通过)
int minNumberInRotateArray(vector<int> rotateArray) {
if (rotateArray.size() <= 0) return 0;
int left = 0;
int right = rotateArray.size() - 1;
int mid = left;
while (rotateArray[left] >= rotateArray[right]) {
if (right - left == 1) {
mid = left;
break;
}
mid = left + (right - left) / 2;
// 下标指向的数字相同,则按照顺序查找
if (rotateArray[left] == rotateArray[right] && rotateArray[mid] == rotateArray[left])
return MinInOrder(rotateArray, left, right);
if (rotateArray[mid] >= rotateArray[left]) left = mid;
if (rotateArray[mid] <= rotateArray[right]) right = mid;
}
return rotateArray[right];
}
};
现在我主要记录二分的进一步应用
二分法的使用范围: 不一定是有序的,这里旋转数组,将有序的数组打乱,我们照样可以用二分法将这个分界点找到。
1. 因为题目中可能出现重复的数字,如果是从重复的数组中间开始旋转,那么后面数组的重复元素则不满足二分性质。所以我们要将后面重复的元素删除。这样就能保证后面的元素都小于前面的元素。不能要等于,所以删除。
2. 删除后,如果最后一个元素和第一个元素比较,大于的话,就是整个单调数组,这是直接返回第一个元素即可
3. 现在就可以用二分查找了。同样left, right 一个指向第一个元素,一个指向最后一个元素。
4. 在mid和第一个元素比较,如果中间的元素比第一个元素大, 则让left指向mid + 1的元素
5. 如果中间的元素比第一个元素小,则让right指向mid的元素。
6. 那么等于的元素放到哪里呢,因为我们将后面等于第一个元素的值删除了,所以这是还等于的话,直接是在前面的数组,所以等于的话,直接让left指向mid+ 1 的元素
7. 下面就看代码
class Solution {
public:
int minNumberInRotateArray(vector<int> arr) {
int n = arr.size() - 1;
if(n<=0) return -1;
while(n > 0 && arr[n] == arr[0]) n--;
// 考虑单调情况
if(arr[n] >= arr[0]) return arr[0];
int l = 0, r = n;
while(l < r) {
int mid = l+r >> 1;
// 下面一定要>= ,只是> 的话,经测试只能通过90的例子
if(arr[mid] >= arr[0]) l = mid + 1; // 这里是跟arr[0]比较大小,
else
r = mid;
}
return arr[r];
}
};