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

题目 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. 前提条件:这两题默认数组无重复元素,如果存在重复数字,逻辑还要再改动
相关推荐
YukiRinLL1 小时前
查找与排序算法:复杂度、稳定性
数据结构·算法·排序算法
徐小夕9 小时前
花了3个月,我们做了一款 AI 版的“飞书文档”
前端·算法·github
Robert--cao10 小时前
移动底盘与机械臂联合规划:开源算法与工程实现
算法
小豆庄10 小时前
Mac 上给视频人脸打码:本地工具的依赖、追踪与推理管线
算法·视觉检测·视频·敏捷流程·纹理贴图
星夜离尘5611 小时前
MATLAB 工程实验:从相邻块均值手写重叠 Allan 偏差,数清有效项
算法·matlab·均值算法
y1su13 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
residual_fan13 小时前
船用柴油机动态机动工况故障诊断:控制感知仿射网络(CAAN)的技术实现
人工智能·算法·数据挖掘·数据分析
其实防守也摸鱼14 小时前
Cylab在线靶场——CTF中的通用技能
linux·数据库·学习·算法·靶场·ctf·学习网络安全
蓝悦无人机14 小时前
《Planning algorithms》读书笔记——第8章 反馈运动规划
算法·读书笔记·反馈运动规划