【算法拾光】二分查找|搜索旋转排序数组 + 寻找旋转排序数组中的最小值

题目 1:搜索旋转排序数组

链接:33. 搜索旋转排序数组 - 力扣(LeetCode)

题目分析

原始数组是升序数组,在某个下标位置进行旋转,数组被拆成两段升序子数组(例如 [4,5,6,7,0,1,2])。 要求:给一个目标值 target,如果 target 在数组中返回下标,不存在返回 -1;数组无重复元素,时间复杂度要求 \(O(logn)\),提示我们必须使用二分查找,不能直接遍历。 难点:数组不再全局有序,直接二分无法判断目标在哪一侧,需要先判断 mid 落在左半有序区还是右半有序区。

算法原理

旋转数组一定有一半区间是有序的:

  1. 取 mid = left + (right - left) / 2
  2. 判断左区间 [left, mid] 是否有序:nums[left] <= nums[mid]
    • 如果左区间有序:再看 target 是否落在 [nums[left], nums[mid]] 之间,落在里面就收缩右边界;否则去右边查找
  3. 如果左区间无序,说明右区间 [mid, right] 一定有序
    • 判断 target 是否落在右有序区间内,落在里面收缩左边界;否则去左边查找
  4. 循环直到 left > right,找不到返回 -1

代码编写

cpp 复制代码
int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target) return mid;
        // 左半段有序
        if(nums[left] <= nums[mid]) {
            if(target >= nums[left] && target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } else {
            // 右半段有序
            if(target > nums[mid] && target <= nums[right]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }
    return -1;
}

题目 2:寻找旋转排序数组中的最小值

链接:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

题目分析

同样是无重复升序数组旋转后的数组,例如 [3,4,5,1,2],我们要找到数组里面最小元素。 最小值就是旋转点,旋转点的特点:最小值比它右边元素小,同时比左边元素小。 全局有序时(旋转 0 次)最小值就是第一个元素。要求 \(O(logn)\),二分。

算法原理

核心判断:比较 nums[mid] 和 nums[right]

  1. 如果 nums[mid] > nums[right]:最小值一定在 mid 的右侧,left = mid + 1
  2. 如果 nums[mid] < nums[right]:最小值在 mid 或者 mid 左侧,right = mid

注意:这里不能写 right = mid - 1,因为 mid 本身有可能就是最小值 循环结束条件 left < right,最后 left == right,就是最小值下标。

代码编写

cpp 复制代码
int findMin(vector<int>& nums) {
    int left = 0, right = nums.size() - 1;
    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] > nums[right]) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return nums[left];
}

本篇总结

本篇两道题是二分查找的高频变种:旋转有序数组二分,不再依赖全局有序,利用「旋转数组必有一半有序」这个性质二分。

  1. 搜索旋转排序数组:区分左右哪一段有序,再判断 target 是否落在有序区间,收缩窗口;适合查找指定值
  2. 寻找旋转排序数组最小值:只比较 mid 和右边界,不断向旋转拐点收敛,循环条件 left < right,最终左右重合就是答案
  3. 易错点:边界判断、mid 计算防止溢出、区分 while(left <= right) 和 while(left < right) 的使用场景
  4. 前提条件:这两题默认数组无重复元素,如果存在重复数字,逻辑还要再改动
相关推荐
是main不是漫5 小时前
【双指针|LeetCode1089 复写零】巧用逆向思路,破解原地覆盖陷阱
数据结构·算法·leetcode
专注仿真14 小时前
知识问答模型用到的算法集详解及代码实例
python·算法·代码·大模型训练·控制算法
罗西的思考15 小时前
【轻量化复刻大模型 / KIMI K3】AttnRes 模块交互式演示
人工智能·算法·机器学习
好悬给我拽开线19 小时前
ZILOT 论文解析
算法
饮水桶_T20 小时前
JAVA IO流笔记
开发语言·数据结构·算法
星夜离尘5621 小时前
MATLAB 离散数据分箱:半开区间、末端点与计数、加权和、平均
算法·matlab
Pniubi21 小时前
力扣76.最小覆盖子串
算法·leetcode·职场和发展
地平线开发者21 小时前
征程6|用 hrt\_model\_exec 看清 YOLOv5x 的输入、原始输出和后处理
人工智能·算法
csdn_aspnet1 天前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
赤生云电竞1 天前
GPU算力共享平台架构实践:任务分发与资源隔离的关键配置
算法·架构·gpu算力