ARTS是什么?
Algorithm:每周至少做一个leetcode的算法题;
Review:阅读并点评至少一篇英文技术文章;
Tip:学习至少一个技术技巧;
Share:分享一篇有观点和思考的技术文章。
Algorithm
题目解析:
在 m x n 的矩阵中放置着一堆橘子。矩阵中有的空格没有放橘子,有的空格放置的是好的橘子,还有的空格放置的是腐烂的橘子。其中腐烂的橘子会在一分钟污染其周围上下左右的好的橘子,让好的橘子也变成腐烂的橘子。问多少分钟后矩阵中的橘子全部变成腐烂的橘子。如果矩阵中的橘子不能全部变成腐烂的就输出 -1。
通过这道题目,我想起来之前做过的一道类似的题目。也是矩阵,但是场景是丧尸和人,每次丧尸都会袭击其周围的人,让其周围的人也变成丧尸。但是那道题目中的矩阵还有障碍物,可能会更难一些。
一般像这样类似场景求步数的题目,都可以考虑用广度优先搜索求解。这道题目中,搜索的起点是那些腐烂的橘子,每一分钟把这一批腐烂的橘子的相邻的橘子加入到队列中去,以此循环往复,最终直到队列中已不再有橘子,就结束循环。循环结束后需要查看我们是否把矩阵中所有的橘子都变腐烂了,没有的话就输出 -1,有的话直接输出步数。
时间复杂度方面也很好计算,每个橘子,也就是矩阵的每个位置只会进出队列一次,因此整体的时间复杂度就是 O(m * n)
空间复杂度,我们用了队列,另外,一个良好的习惯是不改变输入的变量,我们从新创建了一个数组来替代输入数组。总的来说,空间复杂度也是 O(m * n)
。
参考代码:
func orangesRotting(grid [][]int) int {
if len(grid) == 0 {
return 0
}
queue := [][]int{}
m, n := len(grid), len(grid[0])
visited := make([][]int, m)
for i := 0; i < m; i++ {
visited[i] = make([]int, n)
}
countFresh := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 2 {
queue = append(queue, []int{i, j})
visited[i][j] = 2
}
if grid[i][j] == 1 {
visited[i][j] = 1
countFresh++
}
}
}
times := 0
dirX := []int{0, 0, 1, -1}
dirY := []int{1, -1, 0, 0}
for len(queue) != 0 {
size := len(queue)
for i := 0; i < size; i++ {
cur := queue[0]
queue = queue[1:]
for k := 0; k < 4; k++ {
neiX, neiY := cur[0] + dirX[k], cur[1] + dirY[k]
if (neiX < len(visited) && neiX >= 0 &&
neiY < len(visited[0]) && neiY >= 0 && visited[neiX][neiY] == 1) {
queue = append(queue, []int{neiX, neiY})
visited[neiX][neiY] = 2
countFresh--
}
}
}
if len(queue) != 0 {
times++
}
}
if countFresh != 0 {
return -1
}
return times
}
Review
Code Conventions for the JavaScript Programming Language
文章讲述的是 JavaScript 的代码规范。根据不同的 Syntax,给出了一些比较合适的规范,也对应地给出了解释。
为什么需要代码规范
代码规范在团队合作中尤为重要。因为团队中的成员可能技术背景不太相同,编码的习惯不同。如果没有一个统一的规范,团队成员按照各自的习惯写出来的代码风格也不尽相同。但我们总希望的是,一份代码能够长久的保存下来,尽量减少大范围的修改。需要做到这一点,那我们就需要制定一个代码规范,团队里的人都按照代码规范来书写代码,这样可以大大增加团队的协作效率。
JavaScript 中的代码规范
作者这里列了一些常见的规范,如果你的团队没有制定代码规范,那么就可以用作参考。毕竟,就个人来开发说也是一样的,坚持并习惯一个良好的代码规范,对自己的开发效率也是有帮助的。比如说,当你遇到一个模拟两可的情况,就是一个语句貌似以两种方式呈现都挺好的时候,代码规范就可以帮助你果断地做出选择,不需要纠结犹豫。而且,更重要的一点事,代码规范可以让你前后写出来的代码风格保持一致,有助于日后的维护和错误排查。
空格与断行
- 当一个关键字后面跟着一个左括号
(
时,中间需要有空格,例如:while (true) {
- 当函数名后面跟着一个左括号
(
时,不需要加空格,通常的场景是函数调用和函数定义 - 除了
.
,(
,[
,以及单目运算符,每个位运算符合两边都需要有空格 - 每一个逗号
,
后面需要有空格或者是断行 - 对于分号
;
来说,如果分号在语句的结束,那么后面需要跟上断行,如果在for
循环中作为分割,那么分号后面需要有空格 - 当上一行的结束是
{
,[
,(
时,下一行会有 4 个空格的缩进。当这些左括号匹配上的对应的右括号时,往回缩进 - 对于
?
,:
引导的三目运算符。?
,:
会独占一行,比如:let integer = function ( value, default_value ) { value = resolve(value); return ( typeof value === "number" ? Math.floor(value) : ( typeof value === "string" ? value.charCodeAt(0) : default_value ) ); };
注释
建议使用行注释,而不是块注释。行注释可以让编写代码的人尽量用简洁的语言来描述。因为,过于庞杂的注释会误导读者
命名
- 不要用下划线
_
作为变量或者函数名的开始或者结束。因为这样写在某些变成语言(eg. Python)中是把定义的东西看作是 "private" 的。但是 JavaScript 中并不会把这样命名的东西看作是是 "private" 的。因此不要这样做,容易产生误导。如果需要私有化一个变量或者函数,建议考虑使用 "闭包"。 - 另外构造器函数的首字母是需要大些的,这是因为在创建对象的时候,即使你没有使用
new
关键字。JavaScript 也不会报错,但是会产生一些潜在的问题。将构造器函数名的首字母大写,可以帮助你排查和抵御类似的错误 - 要尽量避免使用全局的变量,如果一定要有,那么要把其命名变成全大写的形式
[] 和 {}
用 {}
替代 new Object()
。用 []
替代 new Array()
其它
- 避免使用 ++ 等运算符,这是因为 JS 中的
+
会把一个变量变成 Number 类型,比如下面这个例子:total = subtotal + +myInput.value;
上面的式子其实是在将 myInput.value
转化为 Number 类型后,和 subtotal
相加。对应的更好的写法是:
total = subtotal + Number(myInput.value);
- 不要使用 eval。eval 是将一个输入的字符串当成是表达式来运行。估计是 eval 在早期的时候频繁地被人使用,造成了很多的问题,我看 MDN 上面写的也是最好不要用 eval。
感觉这上面的大部分规范我都有遵守,当然对于一些其它的部分,还是需要制定自己的习惯,毕竟编码方式的统一才是关键。
Tip
这周继续 vim,这次来看看 vim 中的搜索和替换
vim 中的搜索和替换都是支持正则的,对于正则,之前已经记录过了,这里就不重复记录。这里主要记录一下一些注意点:
-
以模式
/
开始的搜索,或是:s
开始的替换,模式中的/
必须写成\/
才行,否则的话,/
的出现会被当成是模式的结束。规避的方式可以考虑用?
来进行反向搜索。在:s
中,可以用其它模式没有的符号作为分隔符,比如说!
或者是#
-
:s
表示的是行替换。:%s
表示的是所有行的替换。:s
和:%s
的格式是:s/搜索/替换/标志
其中,
/
可以换成是其它的模式没有的符号,比如说下面的一些形式::s!搜索!替换!标志 :s#搜索#替换#标志
-
\_.
可以用来匹配换行符 -
\ze
,\zs
用来标识匹配的结束和开始。这在替换的场景下非常好用
下面来看几个例子巩固上面的知识:
-
如何匹配一个完整的单词
比如说,你想要匹配
fetch
这个单词,但是当你输入/fetch
的时候。只要是涵盖 fetch 的字符也会被匹配上,比如说fetchSample
,fetchUser
等等。如果想唯一的匹配
fetch
,我们可以考虑加上单词的开始和结束标志:/\<fetch\>
-
如何匹配 HTML 的 tag
这里可以使用正则下的最短匹配模式,防止贪婪匹配而导致结果涵盖了 tag 之间的东西:
/<.\{-1,}>
-
删除行尾的
//
开始的注释:%s!\s*//.*$!!
-
删除跨行的
/* */
注释:%s!/\*\_.\{-}\*/!!g
尾部加上的
g
标志表示的是允许在一行中多次替换。这里也用到了最短匹配{-}
-
将函数名的首字母改为大写
:%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g
这里用到了
\ze
,表明替换只会替换其前面的\<\(_*\)\([a-z]\w*\)
部分,而不会替换(
。另外这里还用到了正则当中的分组编号。
感觉需要学好正则,搜索的替换才会变得更加地高效。正则这东西要想真正做到应用自如,勤学苦练加日月积累是少不了的。
Share
这是一个文章集。讲的大多是程序员关心的问题。比如说,“如何成为技术大牛”,“该不该跳槽”,“如何学好一门技术” 等等。作者用较为轻松愉悦,略带诙谐的口吻来叙述,看起来一点压力也没有。在这里面,你可以感觉得到一个程序员的成长经历,职业发展,以及一路走来踩过的各种坑。