题目 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof
思路
其实就是一个查找问题,而且是一个排序数组的二分查找问题,只不过题目被这个“旋转数组”稍微的改变了一下,有了一点迷惑性。在刚看到题目的时候,自然反应是从头开始查找,O(n)的时间复杂度,找到递增数组中那个突然变小的数字即可。
但是既然是排序数组,没有更快的方法了吗?答案是有的,本题思考了一下,完全可以使用二分查找,我这里用表格演示一下:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
值 | 3 | 4 | 5 | 6 | 7 | 1 | 2 |
先取数组[0]的值3,二分查找数组[3]的值,值为6,大于3。那么我们就可以将范围缩小到[4] ~ [6]。然后取数组[5]的值,为1,比3小,那么将新的值设置为1,同时将范围改变到[4] ~ [5]。最后取[4]的值,为7,大于1,那么将范围确定到[5],然后输出1.
这样做的思路是这样的,因为数组是一个递增数组经过旋转后得到的,那么如果中间元素的值比开始元素偏大,那么最终答案应该在后半部分,否则在前半部分。这样时间复杂度就降低到了O(logn)。
代码实现
因为实际运行的时候遇到了这种测试用例:[10, 1, 10, 10, 10],因此在遇到middle值与answer相等的情况下,采用分治法,分别对两侧都进行二分查找就是了。结果时间结果还不如直接暴力求解的结果好,有点扼腕叹息。
class Solution {
public:
int minArray(vector<int>& numbers) {
int answer = numbers[0];
if (numbers.size() == 1) {
return answer;
}
int left = 0, right = numbers.size() - 1;
answer = min(minnumber(numbers, left, right, answer), answer);
return answer;
}
int minnumber(vector<int>& numbers, int left, int right, int answer) {
while (left <= right) {
int middle = (left + right) >> 1;
if (numbers[middle] < answer) {
answer = numbers[middle];
right = middle - 1;
}
else if (numbers[middle] < answer) {
left = middle + 1;
}
else {
int a = min(minnumber(numbers, left, middle - 1, answer), minnumber(numbers, middle + 1, right, answer));
if (a < answer) {
return a;
}
else {
return answer;
}
}
}
return answer;
}
};