LeetCode 73,为什么第一反应想到的解法很有可能是个坑?
本文始发于个人公众号:TechFlow,原创不易,求个关注
今天是LeetCode第42篇文章,我们来看看LeetCode第73题矩阵置零,set matrix zeroes。
这题的难度是Medium,通过率在43%左右,从通过率上可以看出这题的难度并不大。但是这题的解法不少,从易到难,有很多种方法。而且解法和它的推导过程都挺有意思,我们一起来看下。
题意
首先我们来看题意,这题的题意很简单,给定一个二维数组。要求我们对这个数组当中的元素做如下修改,如果数组的i行j列为0,那么将同行和同列的元素全部置为0。要求我们直接在原数组上进行修改,而不是返回一个新的数组。
言下之意,要求我们实现一个in-place的方法,而避免额外开辟新的内存。
样例
Input:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
Output:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
近在眼前的解法原来是坑
这题的题意非常简单,解法也非常明显,以至于很多人拿到它都会当做模拟题来解决。即遍历一下数组,如果找到0,那么将它所在的行和列赋值为0,然后继续遍历。
这段逻辑并不难写,我们很容易写出来:
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
if n == 0:
return
m = len(matrix[0])
for i in range(n):
for j in range(m):
# 当我们找到为0的位置之后,将所在的行和列置为0
if matrix[i][j] == 0:
for k in range(m):
matrix[i][k] = 0
for k in range(n):
matrix[k][j] = 0
但是很遗憾的是,这样的做法是错误的,实际上连样例都无法通过。通不过样例的原因也很简单, 因为0具有传递性。
举个简单的例子,假设第0行当中有一个0,那么最后的结果一定是第0行全部被置为0。但问题是我们是在遍历到0的时候来进行的set操作,这样会将第0行的其他元素也置为0。这样当我们遍历到后面的位置之后,会继续传递,从而将一些不该置为0的地方也置为0了。
举个简单的例子,比如:第0行是1 0 0 1。显然由于第0行存在0,所以操作之后的结果一定是全为0。但问题是matrix[0][3]这个位置原本并不为0,但是如果我们在发现matrix[0][1]为0的时候,将它置为0的话,那么当我们后面遍历到matrix[0][3]得到0的时候,会无法判断究竟是这个位置原本就是0,还是前面出现了0导致这一行全部变成了0。这两者的操作是不同的。
眼看着目标就在眼前,好像一伸手就碰得到,但是偏偏好像这一步就是咫尺天涯,怎么也碰不到。这种感觉想想都很难受,我想,当你试着用这种方法去解这道题然后发现不行的时候,一定会有这样的感觉。并且你会发现好像也没有什么很好的办法来优化。
这种情况在正式的算法比赛当中经常遇到,所以专业的竞赛选手有了经验(吃过亏)之后,想出思路的第一时间就会立即转向思考,这样做是不是会有什么坑,或者是考虑不到的情况。严谨一点的同学还会构思几组不同的测试数据进行测试,或者是脑海中模拟算法的运算。
刚不过去只能绕
以前我年轻的时候总是不信邪,有时候明知道这个方法并不好,或者是存在反例,但是仍会坚持想要通过自己的努力想出一个方案来解决它,而不是更换方法。
我不知道有多少人有同样的想法,但是一般来说头铁的毛病最后总是会被治好的。这题算是一个不错的例子,如果你坚持使用模拟的方法来做这道题,只有一种方案就是再创建一个同样大小的数组来作为缓存。当我们遇到0的时候,我们不直接修改原数组中的结果,而是修改缓存,将同行和同列缓存数组中的元素置为0,最后再将缓存数组与原数组合并。
但是显然这不是一种好的方法,因为题目要求in-place的目的就是为了节约空间,我们另外创建了一个同样大小的数组显然违背了题目的本意。
所以头铁到最后还是得认清现状,这个方法不适合这道题,需要更换解法。如果是在比赛当中得出的这个结论,那么很有可能奖牌已经和你没什么关系了。坚持和固执本身也许没有太大的区别, 可能只是出现的场景不一样。
进阶解法
回到这道题本身,我们已经证明了模拟的思路是行不通的,除了一边遍历一边操作可能带来的混乱之外,还有一个点是这样的复杂度很高。因为如果原数据当中如果本身0就很多的话,那么我们会需要不停地操作,极端情况下,如果所有元素都是0,那么我们每一个位置都需要操作一下行列,整体的复杂度会达到。
既然如此,还有什么好的办法吗?
当然是有的,其实也挺明显的,因为对于一个出现的0来说它会影响的范围是固定的,就是所在的行和列,那我们是不是记录下会全部置为0的行和列,最后再遍历一遍数据,看下当前元素是不是出在置为0的范围当中就可以了。这种方法需要我们再创建两个数组,用来存储行和列是否被置为0。
这个解法也很直观,想到了代码应该不难写:
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
if n == 0:
return
m = len(matrix[0])
rows = [0 for _ in range(n)]
cols = [0 for _ in range(m)]
# 记录置为0的行和列
for i in range(n):
for j in range(m):
if matrix[i][j] == 0:
rows[i], cols[j] = 1, 1
# 如果所在行或者列置为0,那么当前位置为0
for i in range(n):
for j in range(m):
if rows[i] or cols[j]:
matrix[i][j] = 0
终极解法
上面的做法虽然通过之后的战绩不太光彩,没能战胜90%以上的提交,但是能够通过,而且算法没有数量级的差距,也算是可以的。如果让我来做,我可能就想到这种方法为止了。但是题目当中明确说了,还有空间复杂度为O(1)的算法,逼得我进一步思考了一下。
一般来说我们都是优化时间复杂度,很少会优化空间复杂度。相比于优化时间,优化空间有时候更加困难。因为有些时候我们可以空间换时间,可以预处理,可以离线计算……方法相对比较多。但优化空间的方法则很少,尤其是很多时候还不能牺牲时间,所以一般来说只能从算法本身来优化,很少有什么套路可以套用。
在这个问题当中,要优化空间复杂度到常数级,那么说明我们连数组都不能用。也就是说不能记录行和列的信息,但是我们也不能用模拟的方法来进行,那么应该怎么办呢?
干想是很难想出来的, 但是我们换个思路,问题就完全不一样了。上面的算法时间复杂度是最优的,空间复杂度不太行,那么有没有办法既使用同样的算法,又能节省空间呢?看起来似乎不可能,但是其实可以,方法说穿了也并不值钱,就是将数据想办法存在已有的地方,而不是另外开辟空间。在这个问题当中,已有的地方当然就只有一个就是原数组。也就是说我们要把每一行和列是否为0的信息记录在原数组当中,比如我们可以把第0行和第0列用来做这个事情。
但这样又会带来另外一个问题,如果第0行和第0列本身当中也有0出现该怎么办?没办法,只能特判了。我们单独用变量来记录第0行和第0列是否被置为0,这样我们就最大化地利用了空间,将空间复杂度降低到了常数级。
代码逻辑和上面一脉相承,只是多了一点骚操作。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
if n == 0:
return
m = len(matrix[0])
row, col = False, False
# 特判0,0的位置
if matrix[0][0] == 0:
row, col = True, True
# 特判第0列是否含0
for i in range(n):
if matrix[i][0] == 0:
col = True
break
# 特判第0行是否含0
for i in range(m):
if matrix[0][i] == 0:
row = True
break
# 将i行,j列是否为0的信息存入matrix当中
for i in range(0, n):
for j in range(0, m):
if matrix[i][j] == 0:
matrix[i][0] = 0
matrix[0][j] = 0
for i in range(1, n):
for j in range(1, m):
# 根据第0行与第0列数据还原
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
# 最后处理第0行与第0列
if row:
for i in range(m):
matrix[0][i] = 0
if col:
for i in range(n):
matrix[i][0] = 0
总结
到这里,这道题就算是分享完了,它的题意简单,但是解法挺多的,我个人感觉也许还存在更好的解法也不一定。
我个人做完这题最大的感受不是这题的思路如何,也不是它涉及的算法如何,而是想到了很多和算法题无关的事情。比如我们生活当中有没有这样看似简单,但是做起来发现一点也不简单的事情?有没有眼看着目标就在眼前,却发现选择的路一开始就是错的呢?
带着这样的思路来做题,会发现题目也变得有意思多了。
今天的内容就是这些,如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。
LeetCode 73,为什么第一反应想到的解法很有可能是个坑?的更多相关文章
- LeetCode缺失的第一个正数
LeetCode 缺失的第一个正数 题目描述 给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数. 进阶:你可以实现时间复杂度为 O(n)并且只使用常数级别额外空间的解决方案吗? ...
- LeetCode 62,从动态规划想到更好的解法
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第36篇文章,我们一起来看下LeetCode的62题,Unique Paths. 题意 其实这是一道老掉牙的题目了 ...
- [LeetCode] Largest Rectangle in Histogram O(n) 解法详析, Maximal Rectangle
Largest Rectangle in Histogram Given n non-negative integers representing the histogram's bar height ...
- leetcode 第188题,我的解法,Best Time to Buy and Sell Stock IV
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255) ...
- [LeetCode] 73. Set Matrix Zeroes 矩阵赋零
Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in-place. Exampl ...
- LeetCode 73. Set Matrix Zeros(矩阵赋零)
Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. click ...
- LeetCode Array Easy 53. Maximum Subarray 个人解法 和分治思想的学习
Description Given an integer array nums, find the contiguous subarray (containing at least one numbe ...
- Java实现 LeetCode 73 矩阵置零
73. 矩阵置零 给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0.请使用原地算法. 示例 1: 输入: [ [1,1,1], [1,0,1], [1,1,1] ...
- LeetCode 861翻转矩阵后得分详细解法
1. 题目内容 有一个二维矩阵 A 其中每个元素的值为 0 或 1 . 移动是指选择任一行或列,并转换该行或列中的每一个值:将所有 0 都更改为 1,将所有 1 都更改为 0. 在做出任意次数的移动后 ...
随机推荐
- spring的aop的注解配置
一.使用注解的方式配置后置通知 第一步,创建通知类LogAdvice 第二步,要在spring主配置文件中声明以注解的方式配置spring aop 第三步,测试 二.其他异常配置 package c ...
- python地图投影转换
一.投影包osr与proj4的使用 1.osr投影转换示例 from osgeo import osr,ogr#定义投影#wgs84source=osr.SpatialReference()sourc ...
- MySQL基础总结(三)
ORDER BY排序 ORDER BY默认是ASC(升序),降序是DESC LIMIT限制查询结果显示条数 LIMIT显示条数 LIMIT偏移量,显示条数 到目前为止有关查询数据的操作(DQL) 更新 ...
- Day_11【集合】扩展案例3_打印最高分的学员姓名、年龄、成绩,打印10个学生的总成绩和平均分,打印不及格的学员信息及数量
分析以下需求,并用代码实现 1.定义Student类 属性: 姓名:String name 年龄:int age 成绩:int score 行为: 空参构造方法 有参构造方法 set和get方法 to ...
- 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...
- Vular开发手记#1:设计并实现一个拼插式应用程序框架
可视化编(rxeditor)辑告一段落,在知乎上发了一个问题,询问前景,虽然看好的不多,但是关注度还是有的,目前为止积累了21w流量,因为这个事,开心了好长一段时间.这一个月的时间,主要在设计制作Vu ...
- 使用 Visual Studio Code 搭建 C/C++ 开发和调试环境
文章目录 1. 安装 C/C++ 插件 2. 安装 MinGW-w64 并配置好环境变量 3. 测试环境变量是否配置正确 4. 创建和设置 C 语言开发工作区 5. 编写你的第一个 C 语言程序 6. ...
- hdu5381 The sum of gcd]莫队算法
题意:http://acm.hdu.edu.cn/showproblem.php?pid=5381 思路:这个题属于没有修改的区间查询问题,可以用莫队算法来做.首先预处理出每个点以它为起点向左和向右连 ...
- hadoop与spark的处理技巧(四)推荐引擎处理技巧
经常一起购买的商品 scala> var file=sc.textFile("/user/ghj/togeterBought") file: org.apache.spark ...
- vue组件试错
[Vue warn]: Property or method "child1" is not defined on the instance but referenced duri ...