本文始发于个人公众号:TechFlow,原创不易,求个关注

今天是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题,感兴趣的话可以去试着做一下这题,看看究竟解法有多大的变化。

如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。

LeetCode 74,直击BAT经典面试题的更多相关文章

  1. BAT经典面试题,深入理解Java内存模型JMM

    Java 内存模型 Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段.静态字段和构成数组对象的元素)的访问方式.试图屏 ...

  2. BAT经典面试题之redis的热KEY问题怎么解决

    引言 讲了几天的数据库系列的文章,大家一定看烦了,其实还没讲完...(以下省略一万字).今天我们换换口味,来写redis方面的内容,谈谈热key问题如何解决.其实热key问题说来也很简单,就是瞬间有几 ...

  3. BAT面试笔试33题:JavaList、Java Map等经典面试题!答案汇总!

    JavaList面试题汇总 1.List集合:ArrayList.LinkedList.Vector等. 2.Vector是List接口下线程安全的集合. 3.List是有序的. 4.ArrayLis ...

  4. iOS经典面试题(转)

    iOS经典面试题   前言 写这篇文章的目的是因为前两天同学想应聘iOS开发,从网上找了iOS面试题和答案让我帮忙看看.我扫了一眼,倒吸了一口冷气,仔细一看,气的发抖.整篇题目30多个没有一个答案是对 ...

  5. Linux 经典面试题

    [Linux  经典面试题] 1. 在Linux系统中,以 文件 方式访问设备 . 2. Linux内核引导时,从文件 /etc/fstab 中读取要加载的文件系统. 3. Linux文件系统中每个文 ...

  6. 115道Java经典面试题(面中率最高、最全)

    115道Java经典面试题(面中率最高.最全) Java是一个支持并发.基于类和面向对象的计算机编程语言.下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改. 代码复用. 增强代码的可 ...

  7. 李洪强iOS经典面试题35-按层遍历二叉树的节点

    李洪强iOS经典面试题35-按层遍历二叉树的节点 问题 给你一棵二叉树,请按层输出其的节点值,即:按从上到下,从左到右的顺序. 例如,如果给你如下一棵二叉树:    3   / \  9  20   ...

  8. 李洪强iOS经典面试题34-求两个链表表示的数的和

    李洪强iOS经典面试题34-求两个链表表示的数的和 问题 给你两个链表,分别表示两个非负的整数.每个链表的节点表示一个整数位. 为了方便计算,整数的低位在链表头,例如:123 在链表中的表示方式是: ...

  9. 李洪强IOS经典面试题 33-计算有多少个岛屿

    李洪强IOS经典面试题 33-计算有多少个岛屿 问题 在一个地图中,找出一共有多少个岛屿. 我们用一个二维数组表示这个地图,地图中的 1 表示陆地,0 表示水域.一个岛屿是指由上下左右相连的陆地,并且 ...

随机推荐

  1. 目前校园百晓生APP与CSDN软件的对比

    不忘初心.注入灵魂 设计之初 在做校园百晓生该APP时,初心是为校园的同学提供便捷的查询校园信息的服务,确实目前的软件已经实现了该功能,但是总感觉缺少一些灵魂,感觉大部分人渴望的重点在于信息而不是自己 ...

  2. [hdu5399 Too Simple]YY

    题意:m个{1,2...n}→{1,2...,n}的函数,有些已知有些未知,求对任意i∈{1,2,...,n},f1(f2(...(fm(i)))=i的方案总数,为了方便简记为F(i) 思路:如果存在 ...

  3. angular controller的使用

    在angular.js最常用的模块就是控制器了,通常我们会这样使用: var myAppModule = angular.module('myApp', []);   myAppModule.cont ...

  4. javaweb学习之路(3)Cookie

    1.Cookies的原理 1)首先浏览器向服务器发出请求. 2)服务器就会根据需要生成一个Cookie对象,并且把数据保存在该对象内. 3)然后把该Cookie对象放在响应头,一并发送回浏览器. 4) ...

  5. windows中的项目拷贝到linux中,部分数据不显示的原因

    linux严格区分大小写,可能是数据表名大小写导致的. 如上传文件不成功,可能是系统权限导致的.

  6. java 查询指定月份的工作日(不包括法定节假日)

    /** * 日期工具类 用于获取指定月份的工作日(除去周末和法定节假日) */ public class DateUtils { public static void main(String[] ar ...

  7. 笨办法学习python-ex51自我理解笔记

    本章节主要讲的是web的工作原理,先大概熟悉记录一下,为以后写Django web框架打下基础. web工作原理: 1.用户从浏览器输入网址----->browser通过电脑中的网络设备(网卡) ...

  8. RabbitMQ及延时队列

    一.简介 我用过RabbirMQ的发布订阅模式,以及一对一的延迟队列. 1.RabbitMQ的有消息确认机制,消费一条则队列中少一条,也有对应的消费到消息及认为是消费成功这样的模式,一般使用前者. 发 ...

  9. .net core kafka 入门实例 一篇看懂

      kafka 相信都有听说过,不管有没有用过,在江湖上可以说是大名鼎鼎,就像天龙八部里的乔峰.国际惯例,先介绍生平事迹   简介 Kafka 是由 Apache软件基金会 开发的一个开源流处理平台, ...

  10. css中height, width默认值

    转载自:https://www.cnblogs.com/heyode/p/5973960.html <body> <div class="wrap"> &l ...