1. 题目来源
链接:旋转数组的最小数字
来源:LeetCode——《剑指-Offer》专项
2. 题目说明
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2]
为 [1,2,3,4,5]
的一个旋转,该数组的最小值为1。
示例 :
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
3. 题目解析
方法一:二分法
这个题目与[每日一题] 142. 寻找旋转排序数组中的最小值II(数组、二分法、分治、多方法)一模一样,有兴趣的也可看看[每日一题] 141. 寻找旋转排序数组中的最小值(数组、二分法、分治、多方法)难度比 142 题偏小一点,大体思路一致。
当数组中存在大量的重复数字时,就会破坏二分查找法的机制,将无法取得 的时间复杂度,又将会回到 。
比如这两种情况:{1, 0, 1, 1, 1}
和 {1, 1, 1, 0, 1}
,
可以发现,当第一个数字和最后一个数字,还有中间那个数字全部相等的时候,二分查找法就崩溃了,因为它无法判断到底该去左半边还是右半边,就无法缩小范围。
针对这种情况,需要打开思路,拿起我们的顺序查找
- 将右指针左移一位(或者将左指针右移一位),略过一个相同数字,这对结果不会产生影响,因为只是去掉了一个相同的数字而已
- 然后对剩余的部分继续用二分查找法,在最坏的情况下,比如数组所有元素都相同,时间复杂度会升到
参见代码如下:
// 执行用时 :4 ms, 在所有 C++ 提交中击败了96.03%的用户
// 内存消耗 :14.5 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
int minArray(vector<int>& numbers) {
int left = 0, 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;
}
return numbers[right];
}
};
方法二:分治法、递归
思想和前一道大致相同,分治、递归即可,不过写法跟之前那道略有不同,只有在 nums[start] < nums[end]
的时候,才能返回 nums[start]
,等于的时候不能返回。
比如 [3, 1, 3]
这个数组,或者当 start
等于 end
成立的时候,也可以直接返回 nums[start]
,后面的操作跟之前那道题相同,每次将区间 [start, end]
从中间 mid
位置分为两段,分别调用递归函数,并比较返回值,每次取返回值较小的那个即可。
参见代码如下:
// 执行用时 :4 ms, 在所有 C++ 提交中击败了96.03%的用户
// 内存消耗 :14.6 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
int minArray(vector<int>& numbers) {
return helper(numbers, 0, numbers.size() - 1);
}
int helper(vector<int>& nums, int start, int end) {
if (start == end)
return nums[start];
if (nums[start] < nums[end])
return nums[start];
int mid = (start + end) / 2;
return min(helper(nums, start, mid), helper(nums, mid + 1, end));
}
};