寻找旋转排序数组中的最小值
字数 2366 2025-11-28 14:29:57
寻找旋转排序数组中的最小值
题目描述:假设一个按照升序排列的数组在某个未知点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2]。请找出其中最小的元素。你可以假设数组中不存在重复元素。
解题过程:
-
问题理解与分析
- 核心特征:数组原本是严格递增(无重复元素)的,但在某个点进行了旋转。这意味着数组由两个递增的子数组拼接而成。
- 关键观察:旋转后的数组虽然整体无序,但它的两部分内部仍然是有序的。最小值恰好是第二个递增子数组的第一个元素,也是整个数组的“拐点”。
- 目标:高效地找到这个最小值。一个简单的方法是遍历整个数组,时间复杂度是 O(n)。但利用数组“部分有序”的特性,我们可以使用二分查找算法在 O(log n) 的时间内解决。
-
算法思路:二分查找
- 核心思想:在标准的二分查找中,我们通过与中间值比较来决定搜索左半部分还是右半部分。在这个问题中,我们需要找到一个判断条件,能够确定最小值位于当前中间点的左侧还是右侧。
- 关键比较:将数组的中间元素
nums[mid]与数组的最右元素nums[high]进行比较。这个比较是解决问题的精髓。- 在一个标准的、未旋转的升序数组中,
nums[mid]总是小于nums[high]。 - 在旋转数组中:
- 如果
nums[mid] < nums[high]:这说明从mid到high的这个区间是严格递增的。那么,最小值一定不在(mid, high]这个区间内(包括mid本身有可能是最小值),最小值只可能存在于[low, mid]这个区间(包括mid)。因此,我们将搜索范围缩小到左半部分,即high = mid。 - 如果
nums[mid] > nums[high]:这说明从mid到high的这个区间不是完全递增的,中间必然包含了旋转的“拐点”(即最小值)。因此,最小值一定存在于(mid, high]这个区间内。我们将搜索范围缩小到右半部分,即low = mid + 1。
- 如果
- 在一个标准的、未旋转的升序数组中,
-
详细步骤
-
步骤1:初始化
定义两个指针,low指向数组起始索引 0,high指向数组末尾索引nums.length - 1。 -
步骤2:循环二分
当low小于high时,执行循环:- 计算中间索引
mid = low + (high - low) / 2。这样写是为了防止(low + high)可能出现的整数溢出。 - 将中间值
nums[mid]与右边界值nums[high]进行比较:- 情况A:如果
nums[mid] < nums[high],则最小值在左半部分,包含mid。执行high = mid。 - 情况B:如果
nums[mid] > nums[high],则最小值在右半部分,不包含mid。执行low = mid + 1。
(注意:由于题目假设无重复元素,所以不会出现nums[mid] == nums[high]的情况。如果存在重复元素,此方法需要调整。)
- 情况A:如果
- 计算中间索引
-
步骤3:返回结果
当循环结束时,low和high会指向同一个元素,这个元素就是数组中的最小值。返回nums[low]或nums[high]。
-
-
举例说明
以题目中的例子nums = [4, 5, 6, 7, 0, 1, 2]为例:- 初始状态:
low = 0,high = 6。 - 第一次循环:
mid = 0 + (6-0)/2 = 3。nums[mid] = 7,nums[high] = 2。7 > 2,属于情况B。最小值在右侧,low = mid + 1 = 4。
- 当前范围:
[4, 6],即子数组[0, 1, 2]。 - 第二次循环:
low = 4,high = 6。mid = 4 + (6-4)/2 = 5。nums[mid] = 1,nums[high] = 2。1 < 2,属于情况A。最小值在左侧,包含mid,high = mid = 5。
- 当前范围:
[4, 5],即子数组[0, 1]。 - 第三次循环:
low = 4,high = 5。mid = 4 + (5-4)/2 = 4。nums[mid] = 0,nums[high] = 1。0 < 1,属于情况A。最小值在左侧,包含mid,high = mid = 4。
- 循环结束:此时
low (4) == high (4),循环条件不满足。返回nums[4] = 0,正确。
- 初始状态:
-
边界情况处理
- 数组未旋转:例如
nums = [1, 2, 3]。算法依然有效,因为每次比较nums[mid]都小于nums[high],会不断将high向左移动,最终low和high会停在索引 0,即最小值 1。 - 单元素数组:例如
nums = [5]。初始low = 0,high = 0,不进入循环,直接返回nums[0]。
- 数组未旋转:例如
-
代码实现(Java)
public int findMin(int[] nums) { int low = 0; int high = nums.length - 1; while (low < high) { int mid = low + (high - low) / 2; if (nums[mid] < nums[high]) { // 最小值在左半部分,包括mid high = mid; } else { // 最小值在右半部分,不包括mid low = mid + 1; } } // 循环结束时,low == high,指向最小值 return nums[low]; }
总结:这道题是二分查找算法的一个经典变体。其核心在于利用旋转数组的特性,通过比较中间元素与右边界元素,来准确地判断最小元素所在的半区,从而将时间复杂度从 O(n) 优化到 O(log n)。理解 nums[mid] 与 nums[high] 比较的逻辑是掌握此题的关键。