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
- 1 1 1 <= n <= 5000 5000 5000
- − 5000 -5000 −5000 <= nums[i] <= 5000 5000 5000
- nums 原来是一个升序排序的数组,并进行了 1 1 1 至 n n n 次旋转
进阶:这道题与 寻找旋转排序数组中的最小值 类似,但 nums 可能包含重复元素。允许重复会影响算法的时间复杂度吗?会如何影响,为什么?
题解
方法一:二分查找
思路
旋转排序数组
n
u
m
s
nums
nums 可能 被拆分为 2 个排序数组
n
u
m
s
1
,
n
u
m
s
2
nums1 , nums2
nums1,nums2,并且
n
u
m
s
1
nums1
nums1 任一元素
>
=
n
u
m
s
2
>= nums2
>=nums2 任一元素;因此,考虑二分法寻找此两数组的分界点
n
u
m
s
[
i
]
nums[i]
nums[i] (即第 2 个数组的首个元素)。
设置 leftleft, rightright 指针在 numsnums 数组两端,midmid 为每次二分的中点:
- 当 n u m s [ m i d ] > n u m s [ r i g h t ] nums[mid] > nums[right] nums[mid]>nums[right] 时, m i d mid mid 一定在第 1 个排序数组中, i i i 一定满足 m i d < i < = r i g h t mid < i <= right mid<i<=right,因此执行 l e f t = m i d + 1 left = mid + 1 left=mid+1;
- 当 n u m s [ m i d ] < n u m s [ r i g h t ] nums[mid] < nums[right] nums[mid]<nums[right] 时, m i d mid mid 一定在第 2 个排序数组中, i i i 一定满足 l e f t < i < = m i d left < i <= mid left<i<=mid,因此执行 r i g h t = m i d right = mid right=mid;
- 当
n
u
m
s
[
m
i
d
]
=
=
n
u
m
s
[
r
i
g
h
t
]
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],在 l e f t = 0 , r i g h t = 4 , m i d = 2 left = 0, right = 4, mid = 2 left=0,right=4,mid=2 时,无法判断 m i d mid mid 在哪个排序数组中。
- 我们采用使相同两端某一端移动下标直到新的
l
l
l 和
r
r
r 的值不相等,来解决此问题(即恢复二分性),证明:
- 此操作不会使数组越界:因为迭代条件保证了 right > left >= 0;
- 此操作不会使最小值丢失:假设 nums[right]nums[right] 是最小值,有两种情况:
- 若
n
u
m
s
[
r
i
g
h
t
]
nums[right]
nums[right] 是唯一最小值:那就不可能满足判断条件
n
u
m
s
[
m
i
d
]
=
=
n
u
m
s
[
r
i
g
h
t
]
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
(
l
o
g
n
)
O(log{n})
O(logn)。整体复杂度为
O
(
n
)
O(n)
O(n) 的。
空间复杂度:
O
(
1
)
O(1)
O(1)