LeetCode第33题:搜索旋转排序数组【python】

简介: LeetCode第33题:搜索旋转排序数组【python】

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

“搜索旋转排序数组”,这是一道中等难度的题目,主要考查对二分查找算法的理解和应用,特别是在处理包含旋转的有序数组时的变体情形。下面我们将详细讨论这个问题。

问题描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

给你一个目标值来搜索,如果数组中存在这个数则返回它的索引,否则返回 -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

二分查找-解题步骤

  1. 确定旋转点
  • 识别数组是如何被旋转的。可以通过比较数组首尾元素的大小来理解旋转的部分。
  1. 二分查找应用
  • 使用二分查找来确定目标值的位置。但由于数组被旋转,我们需要在标准二分查找的基础上进行修改。
  1. 调整二分查找条件
  • 在二分查找的每一步中,首先确定中间元素 mid
  • 确定 mid 分割后的两部分哪一部分是完全排序的。
  • 根据目标值 target 的位置,决定是在有序部分进行标准的二分查找,还是在另一部分继续进行条件修改后的二分查找。
  1. 重复上述过程
  • 直到找到目标值或确定目标值不存在。

代码示例

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = left + (right - left) // 2
            
            # 如果中间的数就是目标值,则直接返回索引
            if nums[mid] == target:
                return mid
            
            # 确定哪一半是有序的
            if nums[left] <= nums[mid]:
                # 如果左边是有序的
                if nums[left] <= target < nums[mid]:
                    right = mid - 1  # 目标在左侧
                else:
                    left = mid + 1   # 目标在右侧
            else:
                # 右边是有序的
                if nums[mid] < target <= nums[right]:
                    left = mid + 1  # 目标在右侧
                else:
                    right = mid - 1 # 目标在左侧
        
        # 如果没有找到目标值
        return -1

代码解释

  1. 初始化指针leftright 指针分别初始化为数组的开始和结束位置。
  2. 二分查找
  • 计算中间位置 mid
  • 检查 nums[mid] 是否等于 target,如果是,则直接返回 mid
  1. 判断有序段
  • 判断左半部分是否有序(即 nums[left] 是否小于等于 nums[mid])。
  • 如果左侧有序,检查 target 是否位于左侧的范围内 (nums[left]nums[mid]),如果是,则调整 right 指针。
  • 如果左侧不有序,则右侧必有序,检查 target 是否位于右侧的范围内 (nums[mid]nums[right]),如果是,则调整 left 指针。
  1. 调整指针:根据上述逻辑,移动 leftright 指针来缩小查找范围。
  2. 返回结果:如果循环结束还没找到 target,返回 -1

性能分析

  • 时间复杂度:O(logn),即使数组被旋转,二分查找仍能保持对数级的时间复杂度。
  • 空间复杂度:O(1),使用了常数空间以存储几个指针。

算法图解

假设数组为 [4, 5, 6, 7, 0, 1, 2],目标值为 0

Initial Array:
Index:   0  1  2  3  4  5  6
Value:   4  5  6  7  0  1  2
         |              |   
        left           right
         ->             <-   
Mid index calculation (3):
         |        |     |   
        left     mid   right
                  |
              Mid Value = 7
 
Step 1: Target not in [4,7], shift to right half
         |              |   
        left           right
                   ->   <-   
                   4    5    6
                   0    1    2
                  left right
                   ->   <-   
Mid index calculation (5):
                   |    |   
                  left mid right
                        |
                    Mid Value = 1
 
Step 2: Target in [0,1], continue in right half
                   |  |   
                  left right
                  mid
                   |
              Mid Value = 0
 
Step 3: Target found at index 4

算法优化

我们还可以从几个角度来考虑优化这个算法,主要是通过简化逻辑、减少计算步骤、和增强代码的鲁棒性和可读性。

优化方向

  1. 简化条件判断逻辑:原算法中包含多个条件判断来确定搜索的方向,这些判断有时可以合并或重新组织以简化逻辑。
  2. 避免不必要的比较:在判断有序部分时,可以通过更少的比较来实现。例如,可以先检查目标是否在当前中值的一侧,如果不是再确认另一侧是否有序。
  3. 提升可读性和维护性:通过提取复杂的判断逻辑为单独的函数,不仅可以使主函数更简洁,也方便未来的维护和修改。
  4. 使用迭代而非递归:虽然原方案已经是迭代,但强调迭代的好处是空间复杂度为 O(1),相比递归方法的空间复杂度 O(logn) 有显著优势。

优化后的代码示例

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 如果数组为空,则直接返回-1
        if not nums:
            return -1
 
        # 初始化左右指针
        left, right = 0, len(nums) - 1
 
        while left <= right:
            # 计算中间位置
            mid = (left + right) // 2
            mid_value = nums[mid]
            left_value = nums[left]
            right_value = nums[right]
 
            # 如果中间的值等于目标值,直接返回索引
            if mid_value == target:
                return mid
 
            # 判断左侧是否有序
            if left_value <= mid_value:  # 左侧是有序的
                # 判断目标值是否在左侧的有序区间内
                if left_value <= target < mid_value:
                    right = mid - 1  # 目标在左侧,移动右指针
                else:
                    left = mid + 1   # 目标不在左侧,移动左指针
            else:                       # 右侧是有序的
                # 判断目标值是否在右侧的有序区间内
                if mid_value < target <= right_value:
                    left = mid + 1  # 目标在右侧,移动左指针
                else:
                    right = mid - 1 # 目标不在右侧,移动右指针
 
        # 如果循环结束还没找到目标值,返回-1
        return -1

代码解释

  • 初始化和条件判断:设置左右指针分别指向数组的起始和结束位置。如果数组为空,直接返回-1。
  • 计算中值和判断有序区间:每次循环计算中间索引mid和对应的值mid_value,并判断左侧或右侧哪个区间是有序的。
  • 目标值位置判断:根据目标值target与中值mid_value的比较结果以及确定的有序区间,来决定是向左区间搜索还是向右区间搜索。
  • 更新指针位置:根据上述判断,更新左指针或右指针的位置,以缩小搜索区间。
  • 返回结果:循环结束如果没有找到目标值,则返回-1。

性能分析

  • 时间复杂度:O(logn),其中n是数组长度。该算法保持了二分查找的时间复杂度,因为每次操作都将搜索区间减半。
  • 空间复杂度:O(1),算法只使用了固定的额外空间。

结论

“搜索旋转排序数组”问题展示了二分查找在复杂场景下的应用。理解数组的分段有序性是解决问题的关键,本题的解法不仅适用于旋转排序数组,也加深了对二分查找应用的理解。优化后的算法尽管在理论的时间复杂度上看不出变化(如都是 O(logn)),实际的运行时间、系统资源利用率及代码的质量和可维护性可能有显著改进。这种改进特别在大数据量处理和高性能计算环境中非常重要。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
6天前
|
Python
在 Python 中,对列表进行排序有两种常用的方法
在 Python 中,对列表进行排序有两种常用的方法
|
5天前
|
IDE 前端开发 开发工具
怎么在isort Python 代码中的导入语句进行排序和格式化
`isort` 是一个Python工具,用于自动排序和格式化代码中的导入语句,提高代码整洁度和可读性。它支持自动排序、保留空白和注释、自定义排序规则、与多种编辑器集成以及命令行使用。安装`isort`可通过`pip install isort`,使用时可直接在Python代码中导入或通过命令行处理文件。示例展示了如何在代码中使用`isort`进行导入排序,包括基本排序、自定义设置和处理多个文件。`isort`适用于标准库、第三方库和自定义模块的导入排序,还可忽略特定导入,并能与IDE和编辑器插件集成,提升开发效率。
|
6天前
|
IDE 开发工具 开发者
isort——Python 代码中的导入语句进行排序和格式化
isort,全称是 "Import Sorting",是一个 Python 工具,用来对 Python 代码中的导入语句进行排序和格式化。它可以帮助我们按照一定的规则对导入的模块进行排序,使得代码更加整洁,易于阅读和维护。
|
9天前
|
C++ Python
二刷力扣--数组
二刷力扣--数组
|
10天前
|
索引
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值
|
11天前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
|
10天前
【LeetCode刷题】前缀和解决问题:742.寻找数组的中心下标、238.除自身以外数组的乘积
【LeetCode刷题】前缀和解决问题:742.寻找数组的中心下标、238.除自身以外数组的乘积
|
10天前
【LeetCode刷题】二分查找:寻找旋转排序数组中的最小值、点名
【LeetCode刷题】二分查找:寻找旋转排序数组中的最小值、点名
|
1天前
|
机器学习/深度学习 人工智能 前端开发
Python中的模块化编程
【6月更文挑战第17天】Python模块化编程与软件架构设计的关键在于拆分任务到独立模块,提高代码的可维护性、可重用性和可扩展性。例如,学生管理系统可分解为录入、查询和删除模块。MVC和MVVM架构模式有助于组织代码,而微服务和函数式编程将在未来发展中扮演重要角色。通过示例代码,读者能学习如何实现这些概念,提升项目开发效率和质量。
146 56
|
8天前
|
测试技术 虚拟化 云计算
GitHub高赞!速通Python编程基础手册,被玩出花了!
随着云时代的来临,Python 语言越来越被程序开发人员喜欢和使用,因为其不仅简单易学,而且还有丰富的第三方程序库和相应完善的管理工具。 从命令行脚本程序到 GUI程序,从图形技术到科学计算,从软件开发到自动化测试,从云计算到虚拟化,所有这些领域都有 Python 的身影。 今天给小伙伴们分享的这份手册采用以任务为导向的编写模式,全面地介绍了 Python 编程基础及其相关知识的应用,讲解了如何利用 Python 的知识解决部分实际问题。
GitHub高赞!速通Python编程基础手册,被玩出花了!