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

【LeetCode】33. 搜索旋转排序数组

文章目录

  • 33. 搜索旋转排序数组
    • 描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 示例 4:
    • 提示:
    • 解题思路
      • 方法一:二分查找(推荐)
      • 方法二:先找旋转点,再二分查找
      • 方法三:线性搜索
    • 代码实现
    • 复杂度分析
    • 算法图解
      • 二分查找过程图
      • 旋转数组结构图
      • 二分查找决策树
    • 边界情况处理
    • 测试用例
    • 完整题解代码

33. 搜索旋转排序数组

描述

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

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= 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:

输入:nums = [3,1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

解题思路

方法一:二分查找(推荐)

核心思想

  • 旋转数组可以分成两个有序部分
  • 通过比较中间元素与边界元素,确定哪一半是有序的
  • 在有序的一半中进行二分查找,在无序的一半中继续递归

算法步骤

  1. 初始化左右指针 left = 0, right = len(nums) - 1
  2. 计算中间位置 mid = (left + right) / 2
  3. 如果 nums[mid] == target,返回 mid
  4. 判断左半部分是否有序:
    • 如果 nums[left] <= nums[mid],左半部分有序
    • 如果 target 在左半部分范围内,在左半部分搜索
    • 否则在右半部分搜索
  5. 否则右半部分有序:
    • 如果 target 在右半部分范围内,在右半部分搜索
    • 否则在左半部分搜索

时间复杂度:O(log n)
空间复杂度:O(1)

方法二:先找旋转点,再二分查找

核心思想

  • 先找到旋转点(最小值的位置)
  • 根据 target 与边界值的比较,确定在哪个有序区间搜索
  • 在确定的有序区间中进行二分查找

时间复杂度:O(log n)
空间复杂度:O(1)

方法三:线性搜索

核心思想

  • 直接遍历数组查找目标值
  • 适用于小规模数据或调试

时间复杂度:O(n)
空间复杂度:O(1)

代码实现

// 二分查找解法
func search(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] == target {return mid}// 判断左半部分是否有序if nums[left] <= 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
}

复杂度分析

方法时间复杂度空间复杂度适用场景
二分查找O(log n)O(1)推荐,最优解
找旋转点+二分O(log n)O(1)思路清晰
线性搜索O(n)O(1)小规模数据

算法图解

二分查找过程图

graph TDA[开始: nums=[4,5,6,7,0,1,2], target=0] --> B[第一次二分: left=0, right=6, mid=3]B --> C{检查 nums[mid]=7}C --> D[左半部分有序: nums[0:3]=[4,5,6,7]]D --> E{target=0 在左半部分范围内?}E -->|否| F[在右半部分搜索: left=4, right=6]F --> G[第二次二分: left=4, right=6, mid=5]G --> H{检查 nums[mid]=1}H --> I[左半部分有序: nums[4:4]=[0]]I --> J{target=0 在左半部分范围内?}J -->|是| K[在左半部分搜索: left=4, right=4]K --> L[第三次二分: left=4, right=4, mid=4]L --> M{检查 nums[mid]=0}M --> N[找到目标值! 返回索引4]style N fill:#90EE90style A fill:#E6F3FFstyle B fill:#FFF2CCstyle G fill:#FFF2CCstyle L fill:#FFF2CC

旋转数组结构图

有序区间
旋转后数组
原始数组
左半部分有序: 4,5,6,7
右半部分有序: 0,1,2
4,5,6,7,0,1,2
0,1,2,3,4,5,6,7
原始有序数组
在位置k旋转
旋转后数组

二分查找决策树

graph TDA[开始二分查找] --> B{检查 nums[mid] == target?}B -->|是| C[返回 mid]B -->|否| D{左半部分有序?<br/>nums[left] <= nums[mid]}D -->|是| E{target 在左半部分范围内?<br/>nums[left] <= target < nums[mid]}E -->|是| F[在左半部分搜索<br/>right = mid - 1]E -->|否| G[在右半部分搜索<br/>left = mid + 1]D -->|否| H{target 在右半部分范围内?<br/>nums[mid] < target <= nums[right]}H -->|是| I[在右半部分搜索<br/>left = mid + 1]H -->|否| J[在左半部分搜索<br/>right = mid - 1]F --> K{left <= right?}G --> KI --> KJ --> KK -->|是| BK -->|否| L[返回 -1]style C fill:#90EE90style L fill:#FFB6C1style A fill:#E6F3FF

边界情况处理

  1. 数组长度为1:直接比较
  2. 数组长度为2:分别比较两个元素
  3. 没有旋转:数组完全有序
  4. 目标值不存在:返回-1
  5. 目标值在边界:正确处理边界条件

测试用例

func main() {// 测试用例1nums1 := []int{4, 5, 6, 7, 0, 1, 2}target1 := 0fmt.Printf("测试用例1: nums=%v, target=%d, 结果=%d\n", nums1, target1, search(nums1, target1))// 测试用例2nums2 := []int{4, 5, 6, 7, 0, 1, 2}target2 := 3fmt.Printf("测试用例2: nums=%v, target=%d, 结果=%d\n", nums2, target2, search(nums2, target2))// 边界测试nums3 := []int{1}target3 := 0fmt.Printf("边界测试1: nums=%v, target=%d, 结果=%d\n", nums3, target3, search(nums3, target3))nums4 := []int{3, 1}target4 := 1fmt.Printf("边界测试2: nums=%v, target=%d, 结果=%d\n", nums4, target4, search(nums4, target4))
}

完整题解代码

package mainimport "fmt"// 方法一:二分查找解法(推荐)
// 时间复杂度:O(log n),空间复杂度:O(1)
func search(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] == target {return mid}// 判断左半部分是否有序if nums[left] <= 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
}// 方法二:先找旋转点,再二分查找
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchFindPivot(nums []int, target int) int {if len(nums) == 0 {return -1}// 找到旋转点(最小值的位置)pivot := findPivot(nums)// 根据target与边界值的比较,确定搜索区间if pivot == 0 {// 数组没有旋转,直接二分查找return binarySearch(nums, target, 0, len(nums)-1)}// 在左半部分搜索if nums[0] <= target && target <= nums[pivot-1] {return binarySearch(nums, target, 0, pivot-1)}// 在右半部分搜索return binarySearch(nums, target, pivot, len(nums)-1)
}// 找到旋转点(最小值的位置)
func findPivot(nums []int) int {left, right := 0, len(nums)-1for left < right {mid := left + (right-left)/2if nums[mid] > nums[right] {left = mid + 1} else {right = mid}}return left
}// 标准二分查找
func binarySearch(nums []int, target, left, right int) int {for left <= right {mid := left + (right-left)/2if nums[mid] == target {return mid} else if nums[mid] < target {left = mid + 1} else {right = mid - 1}}return -1
}// 方法三:线性搜索(用于验证和调试)
// 时间复杂度:O(n),空间复杂度:O(1)
func searchLinear(nums []int, target int) int {for i, num := range nums {if num == target {return i}}return -1
}// 方法四:优化的二分查找解法
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchOptimized(nums []int, target int) int {if len(nums) == 0 {return -1}left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] == target {return mid}// 处理重复元素的情况(虽然题目保证元素互不相同)if nums[left] == nums[mid] && nums[mid] == nums[right] {left++right--continue}// 判断左半部分是否有序if nums[left] <= 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
}// 辅助函数:打印数组和搜索过程
func printSearchProcess(nums []int, target int) {fmt.Printf("数组: %v, 目标值: %d\n", nums, target)// 找到旋转点pivot := findPivot(nums)fmt.Printf("旋转点位置: %d (值: %d)\n", pivot, nums[pivot])// 显示有序区间if pivot == 0 {fmt.Println("数组没有旋转,完全有序")} else {fmt.Printf("左半部分有序区间: [%d, %d]\n", 0, pivot-1)fmt.Printf("右半部分有序区间: [%d, %d]\n", pivot, len(nums)-1)}result := search(nums, target)fmt.Printf("搜索结果: %d\n", result)fmt.Println()
}func main() {fmt.Println("=== 33. 搜索旋转排序数组 ===")// 测试用例1nums1 := []int{4, 5, 6, 7, 0, 1, 2}target1 := 0fmt.Printf("测试用例1: nums=%v, target=%d\n", nums1, target1)fmt.Printf("二分查找解法结果: %d\n", search(nums1, target1))fmt.Printf("找旋转点+二分解法结果: %d\n", searchFindPivot(nums1, target1))fmt.Printf("线性搜索解法结果: %d\n", searchLinear(nums1, target1))fmt.Printf("优化二分查找解法结果: %d\n", searchOptimized(nums1, target1))fmt.Println()// 测试用例2nums2 := []int{4, 5, 6, 7, 0, 1, 2}target2 := 3fmt.Printf("测试用例2: nums=%v, target=%d\n", nums2, target2)fmt.Printf("二分查找解法结果: %d\n", search(nums2, target2))fmt.Printf("找旋转点+二分解法结果: %d\n", searchFindPivot(nums2, target2))fmt.Printf("线性搜索解法结果: %d\n", searchLinear(nums2, target2))fmt.Printf("优化二分查找解法结果: %d\n", searchOptimized(nums2, target2))fmt.Println()// 边界测试用例testCases := []struct {nums   []inttarget intdesc   string}{{[]int{1}, 0, "单个元素,目标不存在"},{[]int{1}, 1, "单个元素,目标存在"},{[]int{3, 1}, 1, "两个元素"},{[]int{3, 1}, 3, "两个元素,目标在开头"},{[]int{1, 3}, 3, "两个元素,未旋转"},{[]int{1, 3}, 1, "两个元素,未旋转,目标在开头"},{[]int{1, 2, 3, 4, 5}, 3, "完全有序数组"},{[]int{5, 1, 2, 3, 4}, 1, "旋转一个位置"},{[]int{2, 3, 4, 5, 1}, 1, "旋转到末尾"},}for _, tc := range testCases {result := search(tc.nums, tc.target)fmt.Printf("%s: nums=%v, target=%d, 结果=%d\n", tc.desc, tc.nums, tc.target, result)}// 详细搜索过程演示fmt.Println("\n=== 详细搜索过程演示 ===")demoCases := []struct {nums   []inttarget int}{{[]int{4, 5, 6, 7, 0, 1, 2}, 0},{[]int{4, 5, 6, 7, 0, 1, 2}, 6},{[]int{3, 1}, 1},{[]int{1, 2, 3, 4, 5}, 3},}for _, demo := range demoCases {printSearchProcess(demo.nums, demo.target)}// 算法正确性验证fmt.Println("=== 算法正确性验证 ===")verifyNums := []int{4, 5, 6, 7, 0, 1, 2}verifyTarget := 5fmt.Printf("验证数组: %v, 目标值: %d\n", verifyNums, verifyTarget)fmt.Printf("二分查找解法: %d\n", search(verifyNums, verifyTarget))fmt.Printf("找旋转点+二分解法: %d\n", searchFindPivot(verifyNums, verifyTarget))fmt.Printf("线性搜索解法: %d\n", searchLinear(verifyNums, verifyTarget))fmt.Printf("优化二分查找解法: %d\n", searchOptimized(verifyNums, verifyTarget))// 验证所有解法结果一致if search(verifyNums, verifyTarget) == searchFindPivot(verifyNums, verifyTarget) &&search(verifyNums, verifyTarget) == searchLinear(verifyNums, verifyTarget) &&search(verifyNums, verifyTarget) == searchOptimized(verifyNums, verifyTarget) {fmt.Println("✅ 所有解法结果一致,算法正确!")} else {fmt.Println("❌ 解法结果不一致,需要检查!")}// 性能测试fmt.Println("\n=== 性能测试 ===")largeNums := make([]int, 10000)for i := range largeNums {largeNums[i] = i}// 旋转数组rotated := append(largeNums[5000:], largeNums[:5000]...)largeTarget := 7500fmt.Printf("大数组测试: 长度=%d, 目标值=%d\n", len(rotated), largeTarget)result := search(rotated, largeTarget)fmt.Printf("二分查找解法结果: %d\n", result)
}

文章转载自:

http://cQm1Xir6.rjdjr.cn
http://lyom6XXY.rjdjr.cn
http://MOWaaAMO.rjdjr.cn
http://zx1H5pKE.rjdjr.cn
http://xU2IAGek.rjdjr.cn
http://Vmf2HXAg.rjdjr.cn
http://pKBrzGus.rjdjr.cn
http://1Cbj1Xna.rjdjr.cn
http://XdURgqOO.rjdjr.cn
http://qNU18cCS.rjdjr.cn
http://kUIzzUxL.rjdjr.cn
http://HLBV8yM4.rjdjr.cn
http://nlRhXYDV.rjdjr.cn
http://DjWzAyAv.rjdjr.cn
http://A2aLeBat.rjdjr.cn
http://g91sXXzz.rjdjr.cn
http://P8v2hUPB.rjdjr.cn
http://E6RkAKqn.rjdjr.cn
http://6XkwpQ4L.rjdjr.cn
http://ftFB9KhE.rjdjr.cn
http://21XgjNAx.rjdjr.cn
http://pMd1Ks1v.rjdjr.cn
http://Zq2xHazS.rjdjr.cn
http://KSQhxjrJ.rjdjr.cn
http://nXv4DKZP.rjdjr.cn
http://DGXIs5pz.rjdjr.cn
http://56R5liU9.rjdjr.cn
http://IN9mFNVr.rjdjr.cn
http://k07xph0f.rjdjr.cn
http://RuXfw7Lr.rjdjr.cn
http://www.dtcms.com/a/381016.html

相关文章:

  • 【代码随想录day 25】 力扣 46. 全排列
  • Java JUC并发集合详解:线程安全容器完全指南
  • 流畅的Python(二) 丰富的序列
  • DPO vs PPO,偏好优化的两条技术路径
  • clickhouse的UInt64类型(countIf() 函数返回)
  • 算法之线性基
  • GlobalBuildingAtlas 建筑物白模数据下载
  • 用pywin32连接autocad 写一个利用遗传算法从选择的闭合图形内进行最优利用率的排版 ai草稿
  • 性能测试工具JvisualVM/jconsole使用
  • 面试题:Redis要点总结(性能和使用)
  • 无卡发薪系统:灵活用工全链条协同的核心枢纽( “数据互通、流程联动” 为核心,将人力招聘、劳务结算、电子合同签约、保险投保深度整合,构建灵活用工管理闭环。)
  • 万物皆可PID:深入理解控制算法在OpenBMC风扇调速中的应用
  • Centos修改主机明后oracle的修改
  • 使用 nanoVLM 训练一个 VLM
  • 2025年- H135-Lc209. 长度最小的子数组(字符串)--Java版
  • 数据库建表练习
  • 使用tree命令导出文件夹/文件的目录树(linux)
  • 【SQL】指定日期的产品价格
  • 在WPF项目中使用阿里图标库iconfont
  • 新能源知识库(91)《新型储能规模化行动方案》精华摘引
  • 51c自动驾驶~合集29
  • Arbess V2.0.7版本发布,支持Docker/主机蓝绿部署任务,支持Gradle构建、Agent运行策略
  • 中科米堆CASAIM自动化三维检测系统-支持批量测量工件三维尺寸
  • 【学习K230-例程19】GT6700-TCP-Client
  • Java链表
  • 【PostgreSQL内核学习:表达式】
  • 步骤流程中日志记录方案(类aop)
  • React.memo 小练习题 + 参考答案
  • Java 的即时编译器(JIT)优化编译探测技术
  • 《计算机网络安全》实验报告一 现代网络安全挑战 拒绝服务与分布式拒绝服务攻击的演变与防御策略(4)