大家好!今天给大家带来的是力扣每日一题第154题。
原文地址:http://leanote.com/blog/post/606faaeeab644139cf0001a6
连续刷题的小伙伴们肯定已经做了三天一模一样的题目了ψ(`∇´)ψ
还没有做过的小伙伴可以看到
昨天的题目:http://leanote.com/blog/post/606e5b31ab64417465000615
前天的题目:http://leanote.com/blog/post/606d9d79ab644172650003ed
题目
已知一个长度为 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 ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析
这道题一看特别有规律,因为是一个有序数组被打乱,而且是从一个节点打乱。我们只需找出这个节点就可以恢复之前的有序数组,那么我们只需要使用一次二分查找就可以找出来这个数字是否存在了。
但是我们思考一个问题,我们寻找这个节点用的是什么方法呢?必须是一个O(nlgn)的方法吧,要不然我们为何不直接使用O(n)的遍历去寻找呢?所以我们确定了我们的思路,就是先使用二分查找找到原数组被打乱的位置,还原出原来的数组,找到最小值也就非常容易了。
思路很美好,可是我们没有写过这种奇奇怪怪的二分耶
那怎么办呢,我们就得思考一下这个二分如何操作了。
我们先做出一个公认的定义:转折点,就是该数组被打断的节点,也就是原数组的末尾,当然,转折点后面的那个数字自然而然的就是我们的最小值了
我们举一个例子:
假设该数组是[7,8,2,3,4,5,6]
他的转折点位于8的位置,最小值位于8的后面以为
按照二分的操作,我们先取出一个中间值,对比一下,有三种情况
n[mid]==n[right]这种情况我们没法区分转折点在哪里,所以我们只能使用O(n)的方法进行寻找,即让right节点向左单步移动
n[mid] > n[right]这种情况来看,mid以左的部分是有序的,最小值可能mid的右方。所以我们就对右边进行二分(left = mid + 1)
n[mid] < n[right]这种情况来看,mid以右的部分是有序的,最小值可能在mid及mid的左方。所以我们就对右边进行二分(right = mid)
思路讲完了,接下来我们看一下代码。
impl Solution {
pub fn find_min(nums: Vec<i32>) -> i32 {
let mut l = 0;
let mut r = nums.len() - 1;
while l < r {
let mid = (l + r) / 2;
if nums[mid] < nums[r] {
r = mid;
} else if nums[mid] > nums[r] {
l = mid + 1;
} else {
r -= 1;
}
}
return nums[r];
}
}
其实有心的小伙伴已经发现了,我们这个程序的整体复杂度已经被特殊情况提升到O(n)了,所以其实也可以简单遍历哦!(是不是很想打人)
class Solution {
public:
int findMin(vector<int>& nums) {
int ret = INT_MAX;
for (auto & num : nums) ret = min(ret, num);
return ret;
}
};