Part1. 我的思路和代码
一般情况:
要求的时间复杂度为O(logn),通过遍历一遍数组来找到最小值的时间复杂度为O(n)
,不满足此要求。由旋转数组的定义可知,数组由两段排好序的部分组成,结合时间复杂度为O(logn),想到二分法
。分析旋转数组的特点,发现搬到数组末尾那段元素的最后一个元素,不会大于旋转后数组的首元素,用图表示如下:
原来数组为非降序(左边),以divider为界变为旋转数组(右边),新的数组出现了一个类似“断崖
”的“非升序”部分,即图右边红色虚线的位置,该位置的元素(原来的end)和旋转数组的第一个元素A依然保持非降序关系,但和最后一个元素B不满足非降序关系(一般情况是这样,后文有特殊情况
),因此通过二分法不断地寻找“断崖”的位置,从断崖处的两个元素找到较小的即为所求
。
具体做法上,先计算旋转数组的中间位置middle,接着比较middle位置的元素同A和B的关系,如果和A不满足非降序关系,那么“断崖”位于区间[A, middle](闭区间
,下同),如果和B不满足则位于区间[middle, B],在新的区间继续同样的过程,直到新区间只有两个元素,最后返回两个元素的较小值。
采用二分思想,时间复杂度为O(logn)。
特殊情况1:
上述做法下,输入为[1 0 1 1 1]时无法通过,因为middle为1,和最左元素A、最右元素B比较时,发现都满足非降序关系。该情况下,不可以将middle随机变为其他值
,因为仍然有可能出现同样的情况,只能从A的下一个元素开始到B的上一个元素为止,逐个寻找“特殊的middle”位置,使该位置或者和A不满足非降序关系,异或
和B不满足非降序关系。
特殊情况2:
加上特殊情况1的处理,输入为[1 2 2 2 2 2]时无法通过,此种情况属于旋转数组和原数组相同的情况,在特殊情况1中,对应找不到“特殊的middle”位置的情况
,此时说明数组已经是非降序排列的,最小元素就是A。
int minNumberInRotateArray(vector<int>& nums) {
int start;
int end;
int middle;
int ans;
start = 0;
end = nums.size() - 1;
while(end-start > 1){
middle = (start+end)/2;
if(nums[start]<=nums[middle] && nums[middle]<=nums[end]){ //特殊情况1
int specialIdx = -1;
for(int i=start+1; i<end; i++){
if(!(nums[start]<=nums[i] && nums[i]<=nums[end])){
specialIdx = i;
break;
}
}
if(specialIdx == -1){ //特殊情况2
ans = nums[0];
return ans;
}
middle = specialIdx;
}
if(nums[start]<=nums[middle]){ //一般情况或特殊情况1的后续
start = middle;
}
else{ //一般情况或特殊情况1的后续
end = middle;
}
}
ans = nums[start]<nums[end] ? nums[start] : nums[end];
return ans;
}
Part2. 其他做法
书中的方法
书中在表述上是“中间元素位于前面的递增子数组”和“中间元素位于后面的递增子数组”等方式,本质上我的思路和该方法应该是相同的。与书中的做法相比,发现我的方法有一个可以优化的地方
:当找到“断崖”的位置时,其实不必比较两个元素的大小关系,右边元素一定是不大于左边元素的,也就是直接返回右边元素即可。
Part3. 心得体会
- 再次体会到题目难易是相对的,即使标注着是简单题,思维没有到位依然很难。这道题对我来说是一道“
并不简单的简单题
”。 - 特殊情况的处理很考验
思维的严谨性
。