题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1: 输入:[3,4,5,1,2] 输出:1
示例 2: 输入:[2,2,2,0,1] 输出:0
题解
单次遍历
由于原数组元素递增排序, 遍历时遇到后面一个元素值比前一个元素值小,则该元素值为数组最小元素; 如一直递增, 则返回初始元素。
class Solution {
public:
int minArray(vector<int>& numbers) {
int n = numbers.size();
for (int i = 0; i + 1 < n; i++) {
if (numbers[i] > numbers[i+1]) {
return numbers[i+1];
}
}
return numbers[0];
}
};
二分查找
算法流程:
- 初始化: 声明 ij双指针分别指向 nums 数组左右两端;
- 循环二分: 设 m=(i+j)/2为每次二分的中点( “/” 代表向下取整除法,因此恒有 i≤m<j),可分为以下三种情况:
- 当 nums[m]>nums[j]时: m一定在 左排序数组 中,即旋转点 x一定在[m+1,j] 闭区间内,因此执行 i=m+1;
- 当 nums[m]<nums[j]时: m一定在 右排序数组 中,即旋转点 x一定在[i,m] 闭区间内,因此执行 j=m;
- 当 nums[m]=nums[j]时: 无法判断 m在哪个排序数组中,即无法判断旋转点 x在 [i,m]还是 [m+1,j] 区间中。解决方案: 执行 j=j−1缩小判断范围。
- 返回值: 当 i=j时跳出二分循环,并返回 旋转点的值 nums[i]即可。
补充思考: 为什么本题二分法不用 nums[m]和 nums[i]作比较?
二分目的是判断 m在哪个排序数组中,从而缩小区间。而在 nums[m]>nums[i]情况下,无法判断 m在哪个排序数组中。本质上是由于 j初始值肯定在右排序数组中; i初始值无法确定在哪个排序数组中。举例如下:
对于以下两示例,当 i=0,j=4,m=2时,有 nums[m] > nums[i] ,而结果不同。
[1, 2, 3, 4 ,5]旋转点 x=0x = 0x=0 : m在右排序数组(此示例只有右排序数组);
[3,4,5,1,2] 旋转点 x=3x = 3x=3 : m在左排序数组。
class Solution {
public:
int minArray(vector<int>& numbers) {
int left=0;
int right=numbers.size()-1;
while(left<right){
int mid=left+(right-left)/2;
if(numbers[mid]>numbers[right]){
left=mid+1;
}
else if(numbers[mid]<numbers[right]){
right=mid;
}
else {
right=right-1;
}
}
return numbers[left];
}
};