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. 在做出任意次数的移动后 ...
随机推荐
- 【华为云技术分享】MongoDB经典故障系列五:sharding集群执行sh.stopBalancer()命令被卡住怎么办?
[摘要] MongoDB sharding集群执行sh.stopBalancer()命令时被卡住怎么办?别慌,华为云数据库来给您支招,收下这份方案指南,让您分分钟远离被自建MongoDB数据库支配的恐 ...
- Linux开机自启动脚本的总结
一.在/etc/rc.local中添加 如果不想将脚本粘来粘去,或创建链接什么的, 则: step1. 先修改好脚本,使其所有模块都能在任意目录启动时正常执行; step2. 再在/etc/rc.lo ...
- 你应该知道的Python3.6、3.7、3.8新特性
很多人在学习了基本的Python语言知识后,就转入应用阶段了,后期很少对语言本身的新变化.新内容进行跟踪学习和知识更新,甚至连已经发布了好几年的Python3.6的新特性都缺乏了解. 本文列举了Pyt ...
- FPGA代码优化方法和准则
- [hdu5375 Gray code]DP
题意:给一个二进制码,其中有一些位上为'?',对每个问号确定是'0'还是'1',最后以它对应的格雷码来取数,第i位为1则取第i个数,求取得的数的和的最大值. 思路:二进制码B转换成格雷码G的方法是,G ...
- [hdu4123]dfs区间化+RMQ
题意:给一个树编号0~n-1,一个数组a[i]为节点i在树上走的最大距离(不重复点),然后求最大的区间,使得区间最大差异小于某个值.dfs求出每个数组,同时区间化.枚举区间左边界,右边界同样递增,类似 ...
- vim(vi)的常用快捷键
Vim 简介 vim是什么?听说它是编辑器之神!神一样的存在,而我却经常用上下左右箭头来移动光标,实在是有点对不起它,所以想着稍微学学一点神的技能吧! 一. vim的三种模式 Nomal mode:默 ...
- vue render 中遇到的问题
以后遇到问题会持续更新 1 render中 判断是否显示 2 render中 属性可以通过判断的形式显示
- Zookeeper入门一篇就够了
谈点分布式 什么是分布式呢? 起初,我们的应用流量比较小,所有东西全部部署在一个服务器,比如全部丢给一个tomcat来处理,顶多做一个tomcat的多节点部署多分,再挂一台Nginx做一下负载均衡就O ...
- CentOS8 右键打开后没有终端
最近研究CentOS8 发现右键打开后没有终端这一项: 1.经过查询发现是没有安装一个包 2.使用命令进行安装并重启: [root@base ~]# yum -y install nautilus- ...