引言
这一道题目和上一道题目是一样的。
寻找旋转排序数组中的最小值
- 🎈 题目链接:
- 🎈 做题状态:
我的解题
旋转后的数组其实就是两个升序数组,也可能就一个,因为旋转n次又回来了。
还是使用二分法来找最小值,每次将 mid 和 right 的元素进行比较:
- 如果 nums[mid] < nums[right] 则说明 最小值在左侧区间。mid可能是最小值。
- 如果 nums[mid] > nums[right] 则说明最小值在右侧区间,mid不可能是最小值。
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
if (n == 1) return nums[0];
if (n == 2) return nums[0] < nums[1] ? nums[0] : nums[1];
int left = 0;
int right = n - 1;
// 一般二分法的循环结束条件是 left <= right 而这里当 left == right 时就找到最小值了
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) // 这两个数不会相等。
{
right = mid;
} else
{
left = mid + 1;
}
}
return nums[left];
}
};
mid 可以和 left 比较吗?
虽然 **mid
和 left
的比较也能确定最小值的区间,但在旋转排序数组中,比较 mid
和 right
通常更直观和有效。让我更详细地解释为什么这样做更合适。
- 比较
mid
和left
的方式:
确实,通过比较 mid
和 left
,你也能确定最小值的位置。特别是:
- 如果
nums[mid] > nums[left]
,那么最小值一定在右半部分(因为left
到mid
一定是升序的)。 - 如果
nums[mid] < nums[left]
,那么最小值一定在左半部分(因为left
到mid
中间部分发生了旋转)。
但这种方法在 nums[mid] == nums[left]
时,不好进一步缩小区间。
2. 问题的关键在于 nums[mid] == nums[left]
的情况
- 如果
nums[mid] == nums[left]
,我们就无法判断是应该把左半部分还是右半部分作为下一步查找的目标。 - 在这种情况下,无法直接决定最小值是在左侧还是右侧,因为这两个值相等。因此,我们需要小心地处理这种情况,通常通过增加
left
来避免死循环,虽然这会导致O(n)
的最坏时间复杂度。
3. 比较 mid
和 right
的优势:
相比之下,比较 mid
和 right
是一个更简单和直观的选择,因为:
nums[mid] < nums[right]
时,意味着右边是升序的,所以最小值一定在左边(包括mid
)。nums[mid] > nums[right]
时,意味着最小值在右边(mid
不可能是最小值),所以我们可以直接将left = mid + 1
。
这种方式不需要处理 nums[mid] == nums[left]
的特殊情况,因为在整数的除法是向下取整的,所以mid只可能等于left(这道题目的前提是nums中的数字都唯一)。
总结:
mid
和left
的比较:可以确定最小值的位置,但需要特别小心nums[mid] == nums[left]
的情况。如果处理不当,会导致时间复杂度退化为O(n)
。mid
和right
的比较:通常更简洁有效,因为它避免了上述问题,并能在O(log n)
时间内找到最小值。
代码优化
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) {
right = mid; // 最小值在左半部分
} else {
left = mid + 1; // 最小值在右半部分
}
}
return nums[left];
}
};
这个方法没有处理 nums[mid] == nums[left]
的特殊情况,因此更加高效和简单。