leetcode二维查找

2023-11-09

 

今天是LeetCode专题43篇文章,我们今天来看一下LeetCode当中的74题,搜索二维矩阵,search 2D Matrix。

这题的官方难度是Medium,通过率是36%,和之前的题目不同,这题的点赞比非常高,1604个赞,154个反对。可见这题的质量还是很高的,事实上也的确如此,这题非常有意思。

题意

这题的题意也很简单,给定一个二维的数组matrix和一个整数target,这个数组当中的每一行和每一列都是递增的,并且还满足每一行的第一个元素大于上一行的最后一个元素。要求我们返回一个bool变量,代表这个target是否在数组当中。

也就是说这个是一个典型的判断元素存在的问题,我们下面来看看两个样例:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true
Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

题解

这题刚拿到手可能会有些蒙,我们当然很容易可以看出来这是一个二分的问题,但是我们之前做的二分都是在一个一维的数组上,现在的数据是二维的,我们怎么二分呢?

我们仔细阅读一下题意,再观察一下样例,很容易发现,如果一个二维数组满足每一行和每一列都有序,并且保证每一行的第一个元素大于上一行的最后一个元素,那么如果我们把这个二维数组reshape到一维,它依然是有序的。

比如说有这样一个二维数组:

[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

它reshape成一维之后会变成这样:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

reshape是numpy当中的说法,也可以简单理解成把每一行串在一起。所以这题最简单的做法就是把矩阵降维,变成一位的数组之后再通过二分法来判断元素是否存在。如果偷懒的话可以用numpy来reshape,如果不会numpy的话,可以看下我之前关于numpy的教程,也可以自己用循环来处理。

reshape之后就是简单的二分了,完全没有任何难度:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        import numpy as np
        arr = np.array(matrix)
        # 通过numpy可以直接reshape
        arr = arr.reshape((-1, ))
        l, r = 0, arr.shape[0]
        if r == 0:
            return False
        # 套用二分
        while l+1 < r:
            m = (l + r) >> 1
            if arr[m] <= target:
                l = m
            else:
                r = m
        return arr[l] == target

正经做法

引入numpy reshape只是给大家提供一个解决的思路,这显然不是一个很好的做法。那正确的方法应该是怎样的呢?

还是需要我们对问题进行深入分析,正向思考感觉好像没什么头绪,我们可以反向思考。这也是解题常用的套路,假设我们已经知道了target这个数字存在矩阵当中,并且它的行号是i,列号是j。那么根据题目当中的条件,我们能够得出什么结论呢?

我们分析一下元素的大小关系,可以得出行号小于i的所有元素都小于它,行号大于i的所有元素都大于它。同行的元素列号小于j的元素小于它,列号大于j的元素大于它。

也就是说,行号i就是一条隐形的分界线,将matrix分成了两个部分,i上面的小于target,i下方的大于target。所以我们能不能通过二分找到这个i呢?

想到这里就很简单了,我们可以通过每行的最后一个元素来找到i。对于一个二维数组而言,每行的最后一个元素连起来就是一个一维的数组,就可以很简单地进行二分了。

找到了行号i之后,我们再如法炮制,在i行当中进行二分来查找j的位置。找到了之后,再判断matrix[i][j]是否等于target,如果相等,那么说明元素在矩阵当中。

整个的思路应该很好理解,但是实现的时候有一个小小的问题,就是我们查找行的时候,找的是大于等于target的第一行的位置。也就是说我们查找的是右端点,那么二分的时候维护的是一个左开右闭的区间。在边界的处理上和平常使用的左闭右开的写法相反,注意了这点,就可以很顺利地实现算法了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        # 初始化,左开右闭,所以设置成-1, n-1
        l, r = -1, n-1
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 小于target的时候移动左边界
            if matrix[mid][m-1] < target:
                l = mid
            else:
                r = mid
                
        row = r
        
        # 正常的左闭右开的二分
        l, r = 0, m
        
        while l+1 < r:
            mid = (l + r) >> 1
            if matrix[row][mid] <= target:
                l = mid
            else:
                r = mid
                
        return matrix[row][l] == target

我们用了两次二分,查找到了结果,每一次二分都是一个O(logN)的算法,所以整体也是log级的算法。

优化

上面的算法没有问题,但是我们进行了两次二分,感觉有些麻烦,能不能减少一次,只使用一次二分呢?

如果想要只使用一次二分就找到答案,也就是说我们能找到某个方法来切分整个数组,并且切分出来的数组也存在大小关系。这个条件是使用二分的基础,必须要满足。

我们很容易在数组当中找到这样的切分属性,就是元素的位置。在矩阵元素的问题当中,我们经常用到的一种方法就是对矩阵当中的元素进行编号。比如说一个点处于i行j列,那么它的编号就是i * m + j,这里的m是每行的元素个数。这个编号其实就是将二维数组压缩到一维之后元素的下标。

我们可以直接对这个编号进行二分,编号的取值范围是确定的,是[0, mn)。我们有了编号之后,可以还原出它的行号和列号。而且根据题目中的信息,我们可以确定这个矩阵当中的元素按照编号也存在递增顺序。所以我们可以大胆地使用二分了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        l, r = 0, m*n
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 还原行号和列号
            x, y = mid // m, mid % m
            if matrix[x][y] <= target:
                l = mid
            else:
                r = mid
        return matrix[l // m][l % m] == target

这样一来我们的代码大大简化,并且代码运行的效率也提升了,要比使用两次二分的方法更快。

总结

这道题到这里就结束了,这题难度并不大,想出答案来还是不难的。但是如果在面试当中碰到,想要第一时间想到最优解法还是不太容易。这一方面需要我们积累经验,看到题目大概有一个猜测应该使用什么类型的算法,另一方面也需要我们对问题有足够的理解和分析,从而读到题目当中的隐藏信息

关于这题还有一个变种,就是去掉其中每行的第一个元素大于上一行最后一个元素的限制。那么矩阵当中元素按照编号顺序递增的性质就不存在了,对于这样的情况, 我们该怎么样运用二分呢?这个问题是LeetCode的240题,感兴趣的话可以去试着做一下这题,看看究竟解法有多大的变化。

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

leetcode二维查找 的相关文章

  • MySQL架构的Server层的执行过程

    1 连接器 主要负责跟客户端建立连接 获取权限 维持和管理连接 2 查询缓存 优先在缓存中进行查询 如果查到了则直接返回 如果缓存中查询不到 在去数据库中查询 3 解析器 分析器 分析器的工作主要是对要执行的SQL语句进行词法解析 语法解析

随机推荐

  • 基于SpringBoot+Async注解整合多线程

    提示 本文没有使用原生的创建线程方式 默认已掌握创建线程的四种方式 全文基于SpringBoot框架 要求读者掌握SpringBoot操作 本人能力有限 如有遗漏或错误 敬请指正 谢谢 文章目录 其他文章 前言 一 为什么要使用多线程 二
  • 计算机 服装生产管理的变化,服装生产管理概述.doc

    PAGE PAGE 182 目 录 TOC o n h z HYPERLINK l To 第一章 服装生产管理概述 HYPERLINK l To 第一节 服装生产概述 HYPERLINK l To 一 服装生产企业的特点 HYPERLINK
  • Yii 2.0集成七牛云

    背景知识 七牛云就是我们常说的图床 什么是图床 可以简单理解为是一种存储图片资源的服务器 本文基于Yii2简单介绍七牛云的使用 1 首先在七牛云平台创建账户 传送门 2 登陆账户之后 点击头部菜单管理控制台 进入之后 点击左侧菜单存储对象
  • 技术岗-网上测评智力题

    A 逻辑推理 1 你让工人为你工作7天 给工人的回报是一根金条 金条平分成相连的7段 你必须在每天结束时给他们一段金条 如果只许你两次把金条弄断 你如何给你 的工人付费 2 请把一盒蛋糕切成8份 分给8个人 但蛋糕盒里还必须留有一份 3 小
  • Qt Plugin

    问题 创建 Qt 插件 方法 1 QML 插件 1 qmldir plugin dll plugin qml 位于同一目录 目录名和模块名相同 2 错误列表如下 no dir no qmldir module module is not i
  • CUDA C编程3 - 并行性衡量指标

    系列文章目录 文章目录 系列文章目录 前言 一 CUDA C并行性衡量指标介绍 二 案例介绍 1 案例说明 2 案例实现 3 结果分析 总结 参考资料 前言 CUDA编程 就是利用GPU设备的并行计算能力实现程序的高速执行 CUDA内核函数
  • 相关系数R-判定系数R方的matlab实现

    相关系数 判定系数 相关系数是最早由统计学家卡尔 皮尔逊设计的统计指标 是研究变量之间线性相关程度的量 一般用字母 r 表示 由于研究对象的不同 相关系数有多种定义方式 较为常用的是皮尔逊相关系数 相关表和相关图可反映两个变量之间的相互关系
  • Table表格(antd-design组件库)简单使用

    1 Table表格 展示行列数据 2 何时使用 当有大量结构化的数据需要展现时 当需要对数据进行排序 搜索 分页 自定义操作等复杂行为时 组件代码来自 表格 Table Ant Design 3 本地验证前的准备 参考文章 react项目
  • java 正则表达式 pattern_Java—正则表达式(Pattern类和Matcher类)

    正则表达式介绍 正则表达式可以用于对字符串的处理 相当于是一个匹配字符串的模板 主要包含查找 替换 分割 提取等操作 Java中通过Pattern和Matcher类提供对正则的支持 字符处理 特殊字符处理 对于特殊字符 前面都要加上 进行转
  • 前端埋点实现

    您好 如果喜欢我的文章 可以关注我的公众号 量子前端 将不定期关注推送前端好文 前端埋点实践 介绍 1 实现自定义hook 监测组件 2 收集数据 3 前端错误捕捉 4 发送后端保存数据 5 收集数据展示 总结 介绍 这段时间博主一直在投入
  • c语言编程单片机实现一个按键顺序按亮,另一个顺序按灭

    Led顺序点亮与熄灭 一次一个 博主是小白 这几天一直在搜索和思考怎么实现我的功能 即一共俩个按键 8个led 现象一 采用移位函数 实现按s1 led顺序点亮 按s2 led顺序熄灭 我实现的是一个一个顺序点亮 一个一个顺序熄灭 incl
  • Git - 查看 commit 提交历史

    查看提交历史 在提交了若干更新 又或者克隆了某个项目之后 如何查看提交历史 git log 官方栗子 运行下面的命令获取该项目 git clone https github com scha 运行 git log 命令 可以获取到的信息 不
  • ‘project‘ is not a registered tag library. Must be one of:

    今天又来记录一下 平时开发中遇到的错误 先看报错 project is not a registered tag library Must be one of 基本可以定位到是没有导入project导致的 那么导入project 代码在这里
  • git的使用(详细教程)通过命令行操作及vscode插件

    个人仓库创建 首先在网页中注册并登录gitee 然后进行如下操作 1 在Gitee页面右上角找点 号点击新建仓库 2 填写一个仓库名称 下移将红框圈起的方框勾选上即可创建仓库 仓库介绍可写可不写 3 创建成功跳到如下界面 4 此时不要关闭该
  • 主数据系统的设计与实现

    1 主数据系统的必要性 随着企业信息化的不断深入 企业建设的业务系统 办公系统等信息系统越来越多 由于规划 预算 实施计划等原因限制 各信息系统建设的步调不一致 规划不统一 导致一个严重的问题 一些基础数据 比如商品编码 客户编码等 在不同
  • Windows环境下TensorFlow的安装及如何在Jupyter Notebook中使用TensorFlow

    最近开始学习TensorFlow 因为自己电脑配置不高 只能在Windows下安装cpu版的TensorFlow 首先安装了最新版的Anaconda 接着使用pip命令安装TensorFlow出现下面的问题 tensorflow 1 1 0
  • linux下tomcat常用命令与配置

    最近经常用到的linux下的命令 重启tomcat ps x 查看pid kill 9 pid 杀死进程 app tomcat bin startup sh 启动tomcat 追踪日志 tail f app tomcat log log 配
  • 【华为OD机试真题2023B卷 JAVA&JS】服务失效判断

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 服务失效判断 知识点图 时间限制 1s 空间限制 32MB 限定语言 不限 题目描述 某系统中有众多服务 每个服务用字符串 只包含字母和数字 长度 lt 10 唯一标识 服务间可能有依
  • java/php/net在线教育平台设计

    本系统带文档lw万字以上 答辩PPT 查重 如果这个题目不合适 可以去我上传的资源里面找题目 找不到的话 评论留下题目 或者站内私信我 有时间看到机会给您发 系统设计 4 1 系统体系结构 在线教育平台的结构图4 1所示 图4 1 系统结构
  • leetcode二维查找

    今天是LeetCode专题43篇文章 我们今天来看一下LeetCode当中的74题 搜索二维矩阵 search 2D Matrix 这题的官方难度是Medium 通过率是36 和之前的题目不同 这题的点赞比非常高 1604个赞 154个反对