当前位置: 首页 > news >正文

LeetCode-33.搜索旋转排序数组-二分查找

LeetCode-33.搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 向左旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

分析:

题干描述的是一个经过向左旋转的升序整数数组(原数组元素互不相同,旋转后分为两段各自升序的子数组,如 [4,5,6,7,0,1,2]),需要在数组中查找目标值 target,找到则返回其下标,未找到返回 - 1,且要求算法时间复杂度为 O (log n),这意味着必须采用二分查找的思路,因为只有二分能达到该时间复杂度。

二分查找下界查找模版

while(l<=r){int mid=(r+l)/2;if(nums[mid]>=target){r=mid-1;ans=mid;}else{l=mid+1;}
}

首先看循环条件 while(l <= r),这个条件决定了循环会覆盖数组中所有可能的元素位置 —— 只要左指针 l 没有超过右指针 r,就意味着当前范围内仍有未检查的元素,需要继续二分。

接着分析内部的核心分支:当 nums[mid] >= target 时,说明当前 mid 位置的元素满足 “大于等于 target” 的条件,它有可能是我们要找的 “第一个符合条件的元素”,但也可能存在更靠左的元素同样满足条件(比如数组中有多个大于等于 target 的元素,我们需要最左侧的那个)。因此,先将 ans 暂存为 mid(记录当前找到的符合条件的位置),再把右指针 r 移到 mid - 1—— 这一步是关键的边界收缩,目的是 “放弃当前 mid 右侧的所有元素”,因为右侧元素即使符合条件,下标也比 mid 大,不可能是 “第一个”,所以收缩右边界,在左半部分继续查找是否有更靠前的符合条件的位置。

再看 else 分支(即 nums[mid] < target),此时 mid 位置的元素不满足 “大于等于 target” 的条件,说明所有在 mid 左侧的元素(比 mid 位置元素更小)也必然不满足条件(假设数组是升序的,这是二分查找的前提),因此无需检查左半部分,直接将左指针 l 移到 mid + 1—— 这一步是通过收缩左边界,将查找范围定位到 mid 右侧,去寻找更大的元素,直到找到第一个大于等于 target 的位置。

整个过程中,边界处理的核心逻辑是 “符合条件则向左收缩找更优解,不符合条件则向右收缩找可能解”:通过 r = mid - 1 不断压缩右边界,确保每次暂存的 ans 都是当前范围内最靠左的符合条件的下标;通过 l = mid + 1 不断压缩左边界,排除所有不可能符合条件的小元素。当循环结束时(l > r),ans 就会保留住 “第一个大于等于 target 的元素下标”,如果数组中所有元素都小于 target,ans 会是初始值(通常需提前初始化,如 ans = -1ans = n,具体视场景而定),这也是边界处理的隐性兜底。

二分查找上界查找

while(l<=r){int mid=(r+l)/2;if(nums[mid]>target){r=mid-1;ans=mid;}else{l=mid+1;}
}

下界查找的目标是 “捕获所有≥target 的元素中最靠左的那个”,而当前模版的目标是 “捕获所有>target 的元素中最靠左的那个”,也就是常说的 “上界查找”(找第一个严格大于 target 的元素下标)。

先看分支判断的核心区别:下界查找中,只要nums[mid] ≥ target就会触发 ans 记录和左移 r,因为 “等于 target” 的元素属于下界查找的目标范畴 —— 即使 mid 位置元素等于 target,也可能存在更靠左的等于 target 的元素,所以需要继续向左收缩范围。但当前模版的分支判断是nums[mid] > target,只有当元素严格大于target 时才会记录 ans 并左移 r,这是因为 “等于 target” 的元素不符合当前模版 “找严格大于” 的目标,此时必须向右收缩范围(else 分支l=mid+1),跳过所有等于 target 的元素,直到找到第一个真正大于 target 的位置。比如数组[1,3,5,7],target=5:下界查找会在nums[mid]=5(mid=2)时记录 ans 并左移 r,最终返回 2(第一个≥5 的下标);而当前模版遇到nums[mid]=5时,会进入 else 分支,l=mid+1=3,继续查找,直到 mid=3(nums [3]=7>5)时记录 ans,最终返回 3(第一个>5 的下标)。

要分析改题目就要抓住旋转数组的核心特性:升序且无重复的数组旋转后,会分裂成两段独立的升序子数组(比如 [4,5,6,7,0,1,2],左段 [4,5,6,7] 和右段 [0,1,2] 均升序,且左段所有元素大于右段)。二分查找的关键是 “利用有序性缩小范围”,这里的核心思路就是:每次二分后,先判断mid所在的子数组是否有序,再将target与有序子数组的边界对比,确定target可能存在的区间,进而收缩左右指针 —— 这本质是把 “部分有序” 的旋转数组,转化为每次二分后的 “单一有序区间”,再套用类似基础二分的边界处理逻辑。

算法的执行过程中,先初始化闭区间[l, r]l=0r=n-1),循环条件while(l<=r)确保覆盖所有可能的元素位置,这和基础二分模版的闭区间处理逻辑一致(避免漏查)。每次计算mid后,首先检查nums[mid]是否直接等于target,若命中则直接返回下标,这是所有二分查找的 “快速出口”,减少不必要的判断。

若未命中,则进入核心的 “有序区间判断与边界收缩” 环节:第一步是确定mid所在的子数组是否有序 —— 由于旋转数组的特性,mid要么落在左段有序子数组(满足nums[0]<=nums[mid],因为左段最小元素是nums[0],且左段所有元素大于右段),要么落在右段有序子数组(nums[0]>nums[mid]时,右段必有序,因为左段无序则右段一定保持升序)。这一步是将旋转数组的 “部分有序” 转化为 “单一有序区间” 的关键,为后续套用基础二分逻辑铺路。

mid在左段有序子数组(nums[0]<=nums[mid])时,接下来判断target是否在左段的有序范围内:若target大于等于左段起点nums[0],且小于nums[mid],说明target只可能在左段有序区间内(因为左段有序,不在这个范围的话,左段其他位置也不可能有target),此时按基础二分逻辑收缩右边界r=mid-1,聚焦左段查找;若target不在这个范围,说明target只能在右段无序区间,于是收缩左边界l=mid+1,转向右段查找。

mid在右段有序子数组(nums[0]>nums[mid])时,判断逻辑类似:若target大于nums[mid],且小于等于右段终点nums[n-1](右段最大元素是nums[n-1]),说明target在右段有序区间内,按基础二分逻辑收缩左边界l=mid+1,聚焦右段查找;若target不在这个范围,说明target在左段无序区间,收缩右边界r=mid-1,转向左段查找。

class Solution {public int search(int[] nums, int target) {int n = nums.length;int l=0,r=n-1;while(l<=r){int mid=(l+r)/2;if(nums[mid]==target){return mid;}if(nums[0]<=nums[mid]){if(nums[0]<=target&&nums[mid]>target){r=mid-1;}else{l=mid+1;}}else{if(nums[mid]<target&&nums[n-1]>=target){l=mid+1;}else{r=mid-1;}}}return -1;}
}
http://www.dtcms.com/a/465085.html

相关文章:

  • R语言基础入门详细教程
  • 用wordpress建立学校网站吗人工智能教育培训机构排名
  • 网站及其建设的心得体会wordpress能做大站吗
  • Java SpringMVC(二) --- 响应,综合性练习
  • 【保姆级教程】VMware Workstation Pro 17安装及基础使用
  • 网站开发源代码mvc电子商务网站建设与管理实训报告
  • Bootstrap4 提示框详解
  • 数据分析硬件配置——选购计算机
  • 在Java中,如何实现封装?
  • 【实录】使用 patch-package 修复第三方 npm 包中的 Bug
  • Warm-Flow 1.8.2版本发布|新增功能和优化,体验更稳定
  • 电池组PACK自动化生产线介绍|深圳比斯特自动化
  • 云手机的挂机功能涉及到哪些内容
  • 手机群控软件在游戏运营中的风险管控技术实现
  • js打开网站做欧美市场的网站
  • MongoDB源码delete分析oplog:从删除链路到核心函数实现
  • 运维面试准备——综合篇(一)
  • 线性代数 · 矩阵 | SVD 与 PCA 应用区别
  • 网站漏洞扫描服务个人怎么做公众号
  • 云计算综合标准化体系建设提供系统性指引
  • 阿里云智能集团首席技术官云栖大会要点总结
  • 6. React useState基础使用:useState修改状态的规则;useState修改对象状态的规则
  • 凡科做的网站怎么打不开了天津做再生资源交易的网站
  • AWS Shield 与海外高防服务器的对比分析
  • CTF攻防世界WEB精选基础入门:cookie
  • Vue 中 props 传递数据的坑
  • Descheduler for Kubernetes(K8s 重调度器)
  • Embedding(嵌入):让机器理解世界的通用语言
  • sql练习题单-知识点总结
  • 网站空间域名续费湖南送变电建设公司 网站