力扣hot100:搜索旋转排序数组和寻找旋转排序数组中的最小值(33,153)

搜索旋转排序数组:二分查找的巧妙应用

今天我在LeetCode上解决了"搜索旋转排序数组"这个问题(题目编号33)。下面我将分享我的解题思路和代码实现。

解题思路

虽然数组被旋转了,但它仍然部分有序,这提示我们可以使用二分查找的变种来解决这个问题。关键在于如何判断目标值位于哪一部分。

  1. 常规二分查找 :首先计算中间索引mid
  2. 判断有序部分
    • 如果nums[left] <= nums[mid],说明左半部分是有序的
    • 否则,右半部分是有序的
  3. 在有序部分中判断目标值的位置
    • 如果在有序部分范围内,则在该部分继续搜索
    • 否则,在另一部分搜索

代码实现

java 复制代码
class Solution {
    public int search(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;

        while(left<=right){

            int mid=(right-left)/2+left;

            if(nums[mid]==target){
                return mid;
            }

            if(nums[0]<=nums[mid]){
                //说明在左部分
                if(nums[left]<=target&&target<nums[mid]){
                    right=mid-1;
                }else{
                    left=mid+1;
                }
            }else{
                //说明在右部分
                if(nums[mid]<target&&target<=nums[right]){
                    left=mid+1;
                }else{
                    right=mid-1;
                }
            }


        }

        return -1;
    }
}

关键点解析

  1. 边界条件处理 :使用left <= right作为循环条件,确保所有元素都被检查。
  2. 防止整数溢出 :计算mid时使用(right - left) / 2 + left而不是(left + right) / 2
  3. 有序部分判断 :通过比较numsnums[mid]来确定哪一部分是有序的。
  4. 目标值范围判断:在确定有序部分后,检查目标值是否在该部分范围内,以决定搜索方向。

复杂度分析

  • 时间复杂度:O(log n),因为每次都将搜索范围减半。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

=========================================================================

寻找旋转排序数组中的最小值:二分查找的精妙应用

解题思路

旋转后的数组虽然不再是完全有序,但仍然保持部分有序的特性,这使得我们可以使用二分查找算法高效解决:

  1. 理解旋转特性:旋转后的数组分为两个有序部分,最小值位于两个有序部分的交界处
  2. 二分查找的关键
    • 比较中间元素和右边界元素
    • 如果中间元素小于右边界元素,说明最小值在左半部分(包括中间元素)
    • 如果中间元素大于右边界元素,说明最小值在右半部分(不包括中间元素)
  3. 循环终止条件:当左指针和右指针相遇时,它们指向的位置就是最小值

代码实现

java 复制代码
class Solution {
    public int findMin(int[] nums) {
        int left=0;
        int right=nums.length-1;

        //当使用 left < right 作为循环条件时,循环结束时 left 和 right 指向同一个位置,而这个位置就是我们要找的目标
        while(left<right){

            int mid=(right-left)/2+left;

            if(nums[mid]<=nums[right]){
                right=mid;
            }else if(nums[mid]>=nums[right]){
                left=mid+1;
            }

        }
        //当 nums[mid] > nums[right] 时,说明最小值在右半部分,且 mid 位置肯定不是最小值,所以应该设置 left = mid + 1
        //当 nums[mid] < nums[right] 时,说明最小值在左半部分,且 mid 位置可能是最小值,所以应该设置 right = mid

        return nums[left];
    }
}

关键点解析

  1. 循环条件选择 :使用left < right而不是left <= right,确保最终left和right会相遇
  2. 边界处理
    • nums[mid] <= nums[right]时,最小值在左半部分(包括mid)
    • nums[mid] > nums[right]时,最小值在右半部分(不包括mid)
  3. 指针移动逻辑
    • 最小值在左侧时:right = mid(mid可能是最小值)
    • 最小值在右侧时:left = mid + 1(mid不可能是最小值)
  4. 终止条件 :当left == right时,指向的就是最小值

复杂度分析

  • 时间复杂度:O(log n),每次迭代都将搜索范围减半
  • 空间复杂度:O(1),只使用了常数级别的额外空间

实例演示

假设输入数组为[4,5,6,7,0,1,2]

  1. 初始:left=0, right=6, mid=3 (值为7)
    • 7 > 2 → 最小值在右侧 → left=mid+1=4
  2. 第二轮:left=4, right=6, mid=5 (值为1)
    • 1 <= 2 → 最小值在左侧 → right=5
  3. 第三轮:left=4, right=5, mid=4 (值为0)
    • 0 <= 1 → 最小值在左侧 → right=4
  4. 循环结束:left=4, right=4 → 返回nums=0

总结

这道题目展示了二分查找算法的强大适应能力。即使数组不是完全有序,通过巧妙地比较中间元素与边界元素的关系,我们仍然可以在对数时间内找到最小值。这种思路在实际应用中非常有用,特别是在处理部分有序或旋转后的数据时。

关键启示:

  1. 理解旋转数组的特性:由两个有序部分组成
  2. 通过比较中间元素和边界元素确定最小值的位置
  3. 精心设计的指针移动逻辑确保高效搜索

掌握了这种二分查找的变种,我们能够解决更多类似的搜索问题,提高算法解决实际问题的能力。