11 旋转数组的最小数字
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
注意这里的非减排序,说明不是严格递增的。存在{0,1,1,2,3,3,4}
测试用例:
1)功能测试(输入的数组是升序排序数组的一个旋转,数组中有重复数字或者没有重复数字) 考虑到有重复数字的情况,易忘
2)边界值的测试(输入升序排序数组如{1,2,3,4,5},只包含一个数字的数组)
3)特殊输入测试(输入nullptr指针)
解题思路:
1)循环遍历寻找最小值,时间复杂度O(n)
//最直观的解法,但是面试时不建议这么写。这种思路没有利用输入的旋转数组的特性。 class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { //循环遍历 int arrSize = rotateArray.size(); if(arrSize<=0)return 0; int minValue=rotateArray[0]; for(int i=0;i<arrSize;i++){ if(minValue>rotateArray[i]) minValue = rotateArray[i]; } return minValue; } }; //34ms
根据旋转数组的特性(1,2,3,4,5)(3,4,5,1,2)(5,1,2,3,4),遍历时找到的第一个最小值,就是数组的最小值。因此添加break中断即可,无需在遍历后面的数字。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
//循环遍历
int arrSize = rotateArray.size();
if(arrSize<=0)return 0;
int minValue=rotateArray[0];
for(int i=0;i<arrSize;i++){
if(minValue>rotateArray[i]){
minValue = rotateArray[i];
break; //提前退出即可,无需在遍历后面的数字
}
}
return minValue;
}
};
2) 二分查找 O(logn) 并考虑旋转数组特性
本题中给出的数组是部分排序的,尝试使用二分查找。考虑数组 {3,4,5,1,2}
分析:两个指针初始分别指向首尾,
[1] 当首元素<=尾元素时,数组是排序的,直接返回第一个元素即可。{1,2,3,4,5}
[2] 当首元素>=尾元素时,获取中间元素:
-- 若中间元素>=前指针,则中间元素属于前面的递增子数组。将前指针移动到中间位置。
-- 若中间元素<=后指针,则中间元素属于后面的递增子数组。将后指针移动到中间位置。
查询的范围缩小一半。
[3] 终止条件:由于第一个指针总是指向前面的递增数组,第二个指针总是指向后面的递增数组。因此它们最终会指向两个相邻的元素,而第二个指针刚好指向最小的元素。 因此终止条件为:两个指针相邻。 (last-first)==1
[4] 上述代码在数组中出现重复数字时,可能会报错 考虑数组{0,1,1,1,1}
其两个旋转数组:{1,0,1,1,1} {1,1,1,0,1}
取中间元素为1,由于首元素=中间元素=尾元素=1,无法判断中间元素时属于哪一个数组。这种情况下需要采用顺序查找(即方法1)。
//实现1
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { int arrSize = rotateArray.size(); if(rotateArray.empty()) return 0; //已排序的递增数组{1,2,3,4,5} 不能有等号:如{1,0,1,1,1},会直接返回0 if(rotateArray[0]<rotateArray[arrSize-1]) return rotateArray[0]; //旋转数组--二分查找 int front = 0, back = arrSize - 1; int mindle = (front + back)/2; while((back-front)!=1){//终止条件:没有达到相邻的时候 //if(arrSize == 1) return rotateArray[0]; //只有一个元素时,不判断,会使用顺序搜索 //只有一个元素时,back=front=0,back-front!=1, 中间元素=front元素=back元素,进入顺序求解
if (rotateArray[front]==rotateArray[mindle] && rotateArray[front]==rotateArray[back])//放在内部 //无法判断中间元素归属哪一个数组,使用顺序查找 return searchMin(rotateArray,arrSize); //属于前面的数组
if(rotateArray[front]<=rotateArray[mindle]) front = mindle;
//属于后面的数组 else if (rotateArray[back]>=rotateArray[mindle]) back = mindle; mindle = (front + back)/2; } return rotateArray[back]; } int searchMin(vector<int> rotateArray,int arrSize){ //用判断为空否:不用,调用前,已经判断了 int resMin = rotateArray[0]; for(int i = 1;i<arrSize;i++) if(rotateArray[i]<resMin){ resMin = rotateArray[i]; break; } return resMin; } };
//实现2 class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { int size = rotateArray.size(); if(size == 0){ return 0; } int left = 0,right = size - 1; int mid = left; // rotateArray[left] >= rotateArray[right] 确保旋转 //如果不满足条件,则说明该数组是排好序的,{1,2,3,4,5}.则返回第一个元素即可。因此mid初始为left while(rotateArray[left] >= rotateArray[right]){ // 终止条件:两指针相邻 if(right - left == 1){ mid = right; //后一个指针即为最小值 break; } mid = left + (right - left) / 2; // rotateArray[left] rotateArray[right] rotateArray[mid]三者相等 // 无法确定中间元素是属于前面还是后面的递增子数组 // 只能顺序查找 if(rotateArray[left] == rotateArray[right] && rotateArray[left] == rotateArray[mid]){ return MinOrder(rotateArray,left,right); } // 中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面 if(rotateArray[mid] >= rotateArray[left]){ left = mid; } // 中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面 else{ right = mid; } } return rotateArray[mid]; } private: // 顺序寻找最小值 int MinOrder(vector<int> &num,int left,int right){ int result = num[left]; for(int i = left + 1;i < right;++i){ if(num[i] < result){ result = num[i]; break; } } return result; } };
2) 二分查找 O(logn) 没有考虑旋转数组特性
对任意数组都可以查找最小值 但是做题时,建议考虑题目中数组的特性进行编程
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { size_t len = rotateArray.size(); if(len == 0) return 0; if(len == 1) return rotateArray[0]; vector<int>::iterator low = rotateArray.begin(); vector<int>::iterator mid; vector<int>::iterator high = rotateArray.end()-1; while(low <= high) { //防止迭代器失效 mid = low + (high - low)/2; if(*mid >*high) //中间元素比最后一个元素大,输入前一个数组 { //mid所对元素>最后的元素,则最小元素一定不是mid,因此移动low指针到mid+1,缩小查找范围 low = mid + 1; } else if(*mid < *high) { //不能确定mid是不是最小元素,因此不能减一:high = mid -1 (error) high = mid; } else { // *mid = *high,high所对的值,在数组中已经存在,可不考虑该值,向前移一位 high = high-1; } if(low == high) //终止条件:low与high指针指向同一个元素(地址) { break; } } //程序结束时,low与high指向相同的地址,返回哪一个都可以 return *low; //return *high; } };