​LeetCode刷题实战33:搜索旋转排序数组

2023-11-14

来源:

https://www.cnblogs.com/techflow/p/12441002.html

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !

今天和大家聊的问题叫做搜索旋转排序数组,我们先来看题面:

https://leetcode-cn.com/problems/search-in-rotated-sorted-array/

You are given an integer array nums sorted in ascending order, and an integer target.

Suppose that nums is rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

If target is found in the array return its index, otherwise, return -1.

题意

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [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

题解

这题非常明显,虽然看似限制了时间复杂度在提升难度,但其实是在给大家提示。一个数组查找元素,时间复杂度还是log(n)这显然是在暗示大家什么。我想大家作为一个成熟的程序员,异性的暗示未必读的懂,但来自出题人的暗示一定能get到【狗头】。

显然,这题需要使用二分法,但是如何使用二分呢,这才是本题的重点。

两次二分

第一种方法非常简单粗暴,因为我们已经知道要二分查找了,然后我们还知道数组分成了两截之后又交换了顺序。所以数组的情况应该是这样的:

对不起,我画得有些抽象,但是精髓传达到了。什么意思呢?就是原本升序的数组分成了两截之后交换顺序,那么现在的数组应该是两端递增的序列拼接构成的。既然是两端序列,那么中间显然有一个断点。我们只要找到这个断点的位置,就容易了,这个问题就变成了在两个有序的数组里查找元素了,那就没有难度了。

但是这个断点怎么找呢?

显然不是打开QQ音乐,而是要使用二分法。因为前面我们已经说了,我们整体的复杂度是log级的,显然我们不可能用遍历来寻找断点。留给我们的就只有一条路,就是二分。

设计这里的二分需要对二分法有一定深入的了解,因为在这个问题当中其实是不满足二分法的使用条件的,因为数组被分成了两个部分,已经不是整体有序了。但是虽然不是整体有序,但仍然是有办法的。

我们先来根据上面的图来分析一下,很容易发现,断点的位置就是全局最小值。我们可以找到左侧部分的最大值,或者是右侧部分的最小值,就是断点的位置。

我们再来分析一下二分时候可能出现的情况,一开始的时候l在最左侧,r在最右侧,m则是两侧都有可能。如果m在左侧部分,那么m位置的值一定大于l,否则一定小于l。所以我们通过比较m和l位置元素的大小关系可以判断m在左侧还是右侧。

如果说我们最终的搜索目标是寻找左侧部分的最大值,那么当m处的值大于l时,则舍弃左侧部分,因为左侧部分已经不可能是答案了。同理,如果m处的值小于l,那么应该舍弃m右侧,因为m右侧都在右边部分,当中是不可能有左侧部分的最大值的,通过这种方法我们也可以使用二分查找,只不过条件和我们常用的二分不太一样。

def binary_search(arr):
    l, r = 0, len(arr)
    while r - l > 1:
        m = (l + r) >> 1
        if arr[m] > arr[l]:
            l = m
        else:
            r = m
    return l

找到断点之后就容易了,就是简单的无脑二分了。我们来看代码:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 找到断点
        l, r = 0, len(nums)
        if r == 0:
            return -1
        
        while r - l > 1:
            m = (l + r) >> 1
            if nums[m] > nums[l]:
                l = m
            else:
                r = m
        
        # 根据target和第0个元素比较大小判断target在左还是右
        if target < nums[0]:
            l, r = r, len(nums)
        else:
            l, r = 0, l+1
            
        if l == len(nums):
            return -1
        
        # 二分查找
        while r - l > 1:
            m = (l + r) >> 1
            if nums[m] <= target:
                l = m
            else:
                r = m
        return l if nums[l] == target else -1

一次二分

虽然我们三下五除二地解决了问题,但是我仍然不够满意,总觉得这种做法不够巧妙,应该还能继续优化。比如说我们能不能不先找断点的位置直接二分呢?

仔细想的话是可以的,尤其是我们已经有了上面这种做法的情况下。

在我们上面查找断点的时候,已经有了一个重要的关键信息,就是我们可以通过m和l位置比大小来确定断点在什么位置。既然我们可以确定断点的位置,那么我们同样可以确定target的位置。

说起来很抽象,我们来看图:

这是一种情况,即m的位置在断点右侧,也就是右侧。那么我们通过判断target和l处的大小关系可以判断target可能在哪个部分。

如果target > nums[l],那么显然target在左侧,所以我们应该让r=m,即抛弃右部分。

如果target < nums[l],这个时候还不能确定范围。因为m左侧可能还有比m小的元素,所以我们还需要判断一下target和nums[m]的关系,来判断抛弃哪个部分。如果target < nums[m],那么抛弃右部分,否则抛弃左部分。

下面来看第二种情况:

同样我们判断target和nums[l]的关系,如果target > nums[l],那么我们需要继续判断它和m的关系,来决定抛弃哪个部分。如果target < nums[l],那么说明我们需要抛弃m左侧的部分。

情况虽然有点复杂,但是我们画个图还是很容易理清楚的。一旦理清楚了,我们就可以一次二分搞定全局。

来看代码:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if len(nums) == 0:
            return -1
        
        l, r = 0, len(nums)
        
        while r - l > 1:
            m = (l + r) >> 1
            # 如果m出现做左侧部分
            if nums[m] >= nums[l]:
                if target >= nums[l]:
                    if target < nums[m]:
                        r = m
                    else:
                        l = m
                # 如果target小于nums[l],需要让l=m+1,因为m这个点也是非法点
                else:
                    l = m+1
            else:
                if target >= nums[l]:
                    r = m
                else:
                    if target >= nums[m]:
                        l = m
                    else:
                        r = m
        return l if l < len(nums) and nums[l] == target else -1

到这里整个题目就分析完了,但是比较搞笑的是虽然我们第二种算法看起来牛哄哄,减少了一次二分,但是提交上去之后的结果反而耗时要长一些。而且相信大家也感觉到了,这种方法实现起来要复杂得多,边界条件很容易写错。所以这点告诉我们一个道理,厉害的方法不一定效果好。

这道题虽然不难,但是挺有意思,打破了我们一直以来对于二分法的认识,不知道会不会给你们带来脑洞大开的感觉。

好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力。

上期推文:

LeetCode1-20题汇总,速度收藏!

LeetCode刷题实战21:合并两个有序链表

LeetCode刷题实战23:合并K个升序链表

LeetCode刷题实战24:两两交换链表中的节点

LeetCode刷题实战25:K 个一组翻转链表

LeetCode刷题实战26:删除排序数组中的重复项

LeetCode刷题实战27:移除元素

LeetCode刷题实战28:实现 strStr()

LeetCode刷题实战29:两数相除

LeetCode刷题实战30:串联所有单词的子串

LeetCode刷题实战31:下一个排列

LeetCode刷题实战32:最长有效括号

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

​LeetCode刷题实战33:搜索旋转排序数组 的相关文章

  • 【固定翼飞机】基于最优控制的固定翼飞机着陆控制器设计研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文章
  • 【GRNN-RBFNN-ILC算法】【轨迹跟踪】基于神经网络的迭代学习控制用于未知SISO非线性系统的轨迹跟踪(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 2 1 第1部分 2 2 第2部分
  • 5_机械臂运动学基础_矩阵

    上次说的向量空间是为矩阵服务的 1 学科回顾 从科技实践中来的数学问题无非分为两类 一类是线性问题 一类是非线性问题 线性问题是研究最久 理论最完善的 而非线性问题则可以在一定基础上转化为线性问题求解 线性变换 数域 F 上线性空间V中的变
  • 在 iOS SDK 中使用短信/彩信发送附件

    在 iOS 7 中 支持通过第三方应用程序在短信中添加附件 我想知道 支持哪些类型的文件作为附件 例如 png pdf 等 我可以通过短信 彩信发送 NSData 吗 例如 dat 格式 这些邮件的收件人是否能够使用 iOS 的 打开方式
  • Android:打开指定多个收件人的短信活动[重复]

    这个问题在这里已经有答案了 我正在尝试通过启动意图来启动手机短信提供商 我下面使用的代码是我用来启动意图的代码 Intent sendIntent new Intent Intent ACTION VIEW StringBuilder ur
  • 从 ASP.NET 网站发送 SMS [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有没有办法使用 Web API 从 ASP NET 网站发送 SMS 我了解网络服务 但不知道如何从我的应用程序调用这些服务 Web 服务
  • 是否有任何手机没有短信收件箱内容提供商?

    是否有任何手机没有典型的短信内容提供商 content sms inbox 或者有什么手机有不同的字段方案 Android 是开源的 因此 从技术上讲 制作定制 ROM 的公司可以停用此类功能 但我认为 如果手机能够发送 接收短信 内容提供
  • Android - Intent.setType() 参数

    有谁知道该方法的所有可用参数 像 图像 或 文件 我在官方文档中没有找到该列表的线索 具体来说 我需要发送联系人 vcf 文件的意图 但输入 file vcf 并没有显示通过短信发送选项 sendIntent setData Uri par
  • 以编程方式发送短信,无需短信编辑器窗口

    直到昨天 我还认为不使用 IOS 短信接口就不可能发送后台短信 这里很多人也保证 然而 今天我下载了一个名为 SmartSender 的新应用程序 它可以安排您的短信 然后自动发送 我测试了它 短信实际上并不是在后台发送的 而是出现一个本地
  • 使用短信验证设备的电话号码

    我正在尝试通过让设备向自身发送短信并自动检查是否已收到短信来验证 Android 设备的电话号码 我怎样才能做到这一点 首先 这需要两个权限 一种用于发送短信 另一种用于接收短信 以下内容需要在您的 AndroidManifest xml
  • 如何在 Android 中发送 vcard/contacts/?vcf var SMS 或 MMS?

    我想修改可以发送 contacts vcard vcf 文件 的Android源代码 彩信或短信 Android默认方式是通过蓝牙 我找了很多方法 但都行不通 我知道vcf格式是这样的 BEGIN VCARD VERSION 2 1 N l
  • Android短信设置唯一ID

    我正在尝试开发一个发送和接收 SMS 消息 除其他外 的 Android 应用程序 我希望我的应用程序短信能够轻松识别 我不想使用 SMS 消息正文作为这个唯一标识符 我认为必须有一个我可以使用的 SMS 消息属性 遗憾的是 我未能找到一个
  • 如何从Android应用程序发送短信而不在设备短信视图中进行记录?

    我想从我的 Android 应用程序发送短信 但我不希望其记录存在于设备消息视图中 我目前正在使用下面的代码 sendSMS etsendernumber getText toString etmessagebody getText toS
  • 发送自动短信

    首先 我们使用 net sql server 我有一位客户对能够在预定时间发送短信的系统感兴趣 除了通过电子邮件网关发送短信之外 我从未做过类似的事情 例如 电子邮件受保护 cdn cgi l email protection 但是 我认为
  • 如何使用GSM模块SIM800和Arduino Uno发送短信?

    我正在尝试通过 SIM800 GSM 模块从 Arduino 发送短信 消息到达给定号码 但格式不正确 它显示 消息格式不支持 我在这里添加了我的代码 非常感谢您的快速回复 include
  • 我无法从 Android 的 Kitkat 版本的收件箱中删除短信

    这是我的短信删除代码 if context getContentResolver delete Uri parse content sms inbox date and body new String ctime mess gt 0 Log
  • Web 应用程序可以使用个人电话发送短信吗

    我有一个客户每月发送大约 5000 条 SMS 消息 他们目前正在 iPhone 上执行此操作 方法是将消息实际输入到手机中 我认为这些信息相当重复 并且通常是针对群体的 他们不使用在线消息网关的原因纯粹是成本 我们可以在澳大利亚使用网关
  • 通过iPhone编程发送短信? [复制]

    这个问题在这里已经有答案了 可能的重复 如何在 iPhone 上以编程方式发送短信 https stackoverflow com questions 10848 how to programmatically send sms on th
  • AT+CMGS 无法正常工作

    我在发送短信时遇到 AT 命令问题 AT CMGS 发送后AT CMGS
  • 如何在swift 2中通过短信发送验证码

    我为我的应用程序构建了一个注册表单 我需要通过短信向用户发送验证码才能完成注册过程 我尝试使用 MFMessageComposeViewController 但它打开设备上的对话框短信 以便用户可以看到代码 我还检查了网络上是否有发送短信的

随机推荐

  • 【NLTK】安装和使用NLTK分词和去停词

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 黄聪 Python NLTK自然语言处理学习 一 环境搭建 http www cnblogs com huangcong archive 2011 08 29 215743
  • Matlab 三角函数(sin)

    a 这是一个三角函数 t 0 0 01 2 pi 从0到2pi 步长是0 01 y sin t plot t y 画坐标
  • java Map集合用stream流的方式遍历

    Map
  • c++基础复习——c++对象模型和this指针

    1 在c 中 成员变量和成员函数分开存储 只有非静态的成员变量才属于类的变量上 成员变量和成员函数是分开存储的 当定义一个空类 求空类的大小 include
  • oracle 从一个oracle导数据到另外一个oracle(二)

    场景 原有数据库A突然宕掉了 新搭建了数据库B应急 A启动后要把A上的数据迁移到B上 限制 1 A数据库是Oracle10g B数据库是Oracle11g 2 A的字符集是AMERICAN AMERICA ZHS16GBK B的字符集是AM
  • java 订单进度 设计,java多线程设计方式之订单模式

    java多线程设计模式之订单模式 Java多线程实现订单模式 客户端线程向服务端发起请求后 请求处理需要较长时间处理 这个时候客户端又需要及时得到一个结果响应 这好比我们去蛋糕店订蛋糕 蛋糕往往需要几个小时才能完成 这个时候店员就会给我一个
  • 小程序中父子组件通信的方法

    引入组件 全局组件 在app json文件中配置usingComponents 多个组件用逗号隔开 最后一个不加逗号 单页面使用的组件 在页面的 json文件中配置usingComponents usingComponents myConp
  • SpringBoot框架实现邮件发送(上)

    文章目录 前言 1 邮件发送类依赖导入 2 配置发件邮箱的信息 3 邮件接收类 4 邮件发送工具类 前言 实现登录注册功能的时候 一些软件总是要手机号验证码或者邮件验证码 手机号验证码功能的实现是需要付费使用的 而且也比较容易搭建 例如阿里
  • 【unity3d】Layers的控制/LayerMask的使用

    文章目录 unity Layers的控制 LayerMask的使用 Layers 概述 演示效果 Layers的设置 gameobject设置Layer 手动设置 代码设置 LayerMask的使用 Camera的culling mask的
  • TypeScript基础学习

    最终还是没有逃过ts的魔爪 哈哈哈也不能说魔爪 工作这段时间 感觉每天都在学习新的知识 最近在看源代码的时候看到了部分源码是用ts写的 之前没接触过 今天就来学习一下ts 文章参考 TypeScript超详细入门教程 TypeScript
  • python练习题3

    1 数列翻转 reverse 问题描述 编写程序对列表中的数据进行翻转转换 即将数组中第一个数和最后一个数交换 第二个数和倒数第二个数交换 依此类推 样例输入 4 100 200 300 400 样例输出 400 300 200 100 a
  • bpython bs4用哪个解释器好_Python之解BS4库如何安装与使用?正确方法教你

    Beautiful Soup 库一般被称为bs4库 支持Python3 是我们写爬虫非常好的第三方库 因用起来十分的简便流畅 所以也被人叫做 美味汤 目前bs4库的最新版本是4 60 下文会介绍该库的最基本的使用 具体详细的细节还是要看 官
  • Cocos2d-android游戏引擎

    什么是游戏引擎 游戏引擎是指一些已编写好的可编辑游戏系统或者一些交互式实时图像应用程序的核心组件 这些系统为游戏设计者提供各种编写游戏所需的各种工具 其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始 Cocos2d家族 coc
  • 数据结构小知识------时间与空间复杂度

    本章思维导图 一 时间复杂度 1 1时间复杂度的概念 什么是时间复杂度呢 时间复杂度其实就是一个程序运行时它的指令运行的次数 在这里 程序默认每条指令的运行时间是一样的 所以时间复杂度就可以理解为是程序内指令的运行次数 说一千道一万 不如来
  • Java 使用EasyExcel解析导入的Excel文件

    最近在做项目时 有遇到需要使用excel导入的场景 以前也有写过使用 Apache poi 来解析导入数据 但整体解析逻辑比较繁琐 封装成工具类后也不是很好用 这个可能是我个人技术原因 和poi无关 这次开发时 在网上找了个更加简洁的方式
  • Python循环控制语句

    Python循环控制语句 生活中循环的例子也很多 例如 听歌的时候进行循环等等 程序中循环的效果和生活中的循环效果相同 Python中的循环是往复的执行某一段代码 结构while循环 初始条件设置 通常是一个计数器 来控制条件表达式是否成立
  • OpenStack nova-compute 报TooOldComputeService版本过低问题

    项目场景 安装openstack的nova compute部分 问题描述 启动nova conductor时报错 查看nova conductor log 发现如下错误 Current Nova version does not suppo
  • android aosp,安卓源码AOSP下载使用的正确姿势

    安卓源码AOSP下载使用的正确姿势 从同步源码到编译完成 整个过程应至少准备200G空间 编译时需要的内存数与编译线程数相关 博主实测比较极限的配置是4核8G 超过这个范围将触发swap交换导致编译速度急剧下降 开始搞 注 以下 号所有内容
  • mac运行ps特别慢_PS CC 2019 太卡,运行特别慢?这几个优化提速技巧我再说一遍...

    只要设置好这几个选项 让你的 PS CC 2019 运行如飞 曾经写过关于PS优化提速的教程 但总有粉丝问我PS很卡很慢 怎么办 所以 这几个核心的 PS 优化提速技巧我再说一遍 先声明一下 我这里讲的优化提速是指你电脑配置足够的情况下PS
  • ​LeetCode刷题实战33:搜索旋转排序数组

    来源 https www cnblogs com techflow p 12441002 html 算法的重要性 我就不多说了吧 想去大厂 就必须要经过基础知识和业务逻辑面试 算法面试 所以 为了提高大家的算法能力 这个公众号后续每天带大家