Arts 第七十周(8/17 ~8/23)

132 阅读8分钟

ARTS是什么?
Algorithm:每周至少做一个leetcode的算法题;
Review:阅读并点评至少一篇英文技术文章;
Tip:学习至少一个技术技巧;
Share:分享一篇有观点和思考的技术文章。

Algorithm

LC 994. Rotting Oranges

题目解析

在 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

漫谈程序员

这是一个文章集。讲的大多是程序员关心的问题。比如说,“如何成为技术大牛”,“该不该跳槽”,“如何学好一门技术” 等等。作者用较为轻松愉悦,略带诙谐的口吻来叙述,看起来一点压力也没有。在这里面,你可以感觉得到一个程序员的成长经历,职业发展,以及一路走来踩过的各种坑。