问题背景
我们有一个经过旋转的升序数组,旋转意味着把数组的尾部元素移到前面,原数组的顺序被打乱。我们需要找到旋转后数组中的最小值,且要求时间复杂度为 O(log n),这提示我们应该使用二分查找来解决问题。
例如:
- 输入:[3,4,5,1,2],原数组:[1,2,3,4,5],旋转了 3 次,最小值为 1。
- 输入:[4,5,6,7,0,1,2],原数组:[0,1,2,4,5,6,7],旋转了 4 次,最小值为 0。
解题思路
旋转排序数组的最小值一定位于“数组被旋转的切点”处。通过二分查找,我们可以有效地定位这个切点。
二分查找的关键点:
- 如果
nums[mid]
小于nums[right]
,说明最小值位于左半边,右边界收缩。 - 否则,最小值在右半边,左边界收缩。
代码实现
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int left = 0, right = n - 1; // 初始化左右边界
while (left < right) {
int mid = left + (right - left) / 2; // 防止溢出的写法
// 如果中间值小于右边界值,说明最小值可能在中间左侧
if (nums[mid] < nums[right]) {
right = mid;
} else {
// 否则最小值在右半部分
left = mid + 1;
}
}
return nums[left]; // left 和 right 会收敛到最小值位置
}
}
代码解释
- 初始化边界:
left = 0
,right = n - 1
,这是整个数组的初始范围。 - 循环条件: 在每次循环中,我们判断
nums[mid]
是否小于nums[right]
。如果是,说明最小值位于左半边;否则,最小值在右半边。 - 更新边界: 根据条件更新左右边界,直到
left
和right
相遇,最终定位到最小值的位置。
时间复杂度分析
- 每次二分查找都会将搜索范围缩小一半,因此时间复杂度为 O(log n),满足题目要求。
边界条件处理
- 特殊情况: 如果数组的长度为 1,直接返回该元素。
- 没有旋转的情况: 如果数组没有旋转,最小值是数组的第一个元素。
进一步的优化与思考
通过此解法,我们利用了二分查找的特性,使得算法可以在 O(log n) 的时间复杂度内解决问题。这种方式相较于 O(n) 的暴力解法在性能上有了显著提升。
练习题
- 给定一个旋转排序数组,如何找到旋转的次数?
- 如果数组中的元素不是互不相同的,如何修改代码?
通过优化后的内容,文章不仅清晰简洁,而且更易于读者理解。同时,附加的练习题也能帮助读者进一步巩固所学的知识,提升他们的思考深度。