Leetcode(154)——寻找旋转排序数组中的最小值 II
题目
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
- 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
- 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须尽可能减少整个过程的操作步骤。
示例 1:
输入:nums = [1,3,5]
输出:1
示例 2:
输入:nums = [2,2,2,0,1]
输出:0
提示:
- n == nums.length
- 111 <= n <= 500050005000
- −5000-5000−5000 <= nums[i] <= 500050005000
- nums 原来是一个升序排序的数组,并进行了 111 至 nnn 次旋转
进阶:这道题与 寻找旋转排序数组中的最小值 类似,但 nums 可能包含重复元素。允许重复会影响算法的时间复杂度吗?会如何影响,为什么?
题解
方法一:二分查找
思路
旋转排序数组 numsnumsnums 可能 被拆分为 2 个排序数组 nums1,nums2nums1 , nums2nums1,nums2,并且 nums1nums1nums1 任一元素 >=nums2>= nums2>=nums2 任一元素;因此,考虑二分法寻找此两数组的分界点 nums[i]nums[i]nums[i] (即第 2 个数组的首个元素)。
设置 leftleft, rightright 指针在 numsnums 数组两端,midmid 为每次二分的中点:
- 当 nums[mid]>nums[right]nums[mid] > nums[right]nums[mid]>nums[right] 时,midmidmid 一定在第 1 个排序数组中,iii 一定满足 mid<i<=rightmid < i <= rightmid<i<=right,因此执行 left=mid+1left = mid + 1left=mid+1;
- 当 nums[mid]<nums[right]nums[mid] < nums[right]nums[mid]<nums[right] 时,midmidmid 一定在第 2 个排序数组中,iii 一定满足 left<i<=midleft < i <= midleft<i<=mid,因此执行 right=midright = midright=mid;
- 当 nums[mid]==nums[right]nums[mid] == nums[right]nums[mid]==nums[right] 时,是此题对比 153题 的难点(原因是此题中数组的元素可重复,难以判断分界点 ii 指针区间);
- 例如 [1,0,1,1,1][1, 0, 1, 1, 1][1,0,1,1,1] 和 [1,1,1,0,1][1, 1, 1, 0, 1][1,1,1,0,1],在 left=0,right=4,mid=2left = 0, right = 4, mid = 2left=0,right=4,mid=2 时,无法判断 midmidmid 在哪个排序数组中。
- 我们采用使相同两端某一端移动下标直到新的 lll 和 rrr 的值不相等,来解决此问题(即恢复二分性),证明:
- 此操作不会使数组越界:因为迭代条件保证了 right > left >= 0;
- 此操作不会使最小值丢失:假设 nums[right]nums[right] 是最小值,有两种情况:
- 若 nums[right]nums[right]nums[right] 是唯一最小值:那就不可能满足判断条件 nums[mid]==nums[right]nums[mid] == nums[right]nums[mid]==nums[right],因为
mid < right(left != right 且 mid = (left + right) // 2 向下取整);

代码实现
Leetcode 官方题解:
class Solution {
public:
int findMin(vector<int>& nums) {
int low = 0;
int high = nums.size() - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
}
else if (nums[pivot] > nums[high]) {
low = pivot + 1;
}
else {
high -= 1;
}
}
return nums[low];
}
};
我自己的:
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int l = 0, r = nums.size() - 1, mid;
// 减去重复值,恢复二段性,但是不能全删除,要保留同样数字至少1个(和 81 题不一样)
while(nums[l] == nums[r] && l < r) l++; // 避免[1,1]
while(l <= r){
mid = (l+r)/2;
if(nums[l] <= nums[r]) break;
else{
if(nums[mid] >= nums[l]) l = mid + 1; // 删除左区间
else r = mid; // 删除右侧区间
}
}
return nums[l];
}
};
复杂度分析
时间复杂度:恢复二段性处理中,最坏的情况下(考虑整个数组都是同一个数)复杂度是 O(n)O(n)O(n),而之后的找旋转点是「二分」,复杂度为 O(logn)O(log{n})O(logn)。整体复杂度为 O(n)O(n)O(n) 的。
空间复杂度:O(1)O(1)O(1)
本文介绍了一种算法,用于在可能存在重复元素的旋转排序数组中找到最小值。通过二分查找法优化搜索过程,特别针对数组元素可能重复的情况进行处理。
139

被折叠的 条评论
为什么被折叠?



