Arts 第五十四周(3/30 ~4/5)

344 阅读6分钟

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

Algorithm

LC 406. Queue Reconstruction by Height

题目解析

题目输入是一个二维数组,这个数组的元素是被打乱的,每个元素是个二元祖,表示的一个人的身高以及他前面比他高或者和他一样高的人的个数,让你基于此重新排序。

在做这道题之前,先摒弃一个观念就是:在还没有完全想出可行解的时候就去思考时间复杂度。除非题目对时间复杂度或是输入数据规模有硬性要求,你可以基于题目要求的时间复杂度来思考解题的方向。但是一般的情况下,我们都是试着先去想出可行解,然后根据可行解一步步的优化,不然的话,你做题很容易就会没有思路,因为你心中你所猜测的那个最优时间复杂度会不自不觉地帮你排除很多 “可能的解”。

回到这道题,我们先试着找出可行解。直觉告诉你这是一个和排序相关的题目,但是这其中的两个变量让排序变得棘手,我们不仅需要考虑身高,还需要考虑前面的人数。突破口在哪?当然了,光排序肯定是不够的,我们还需要一些额外的操作,你可以想象一些,我们要对这一堆人(元素)进行排序,每个人都有属于他的位置,我们的工作是将人一个个放到属于他的位置,那我们该怎么放,或是说以一个什么样的顺序去放,才不会出错?

如果我们先放个子矮的是怎么样的情况,考虑例子 [[5,0],[6,0],[7,0]]。我们先放置元素 [5,0],将其放在第一个位置,那么接下来考虑 [6,0],这时就会出现问题,如果光考虑 [6,0] 本身,它其实应该被放在第一个位置,但是问题是如果把 [6,0] 放到第一个位置,之前放置的 [5,0] 就不正确了,那你可能会说,那就把 [6,0] 放到 [5,0] 的后面呗,这个例子下是可以,但是如果例子是 [[5,1],[6,0],[7,0]] 呢?也就是说你考虑当前元素的时候还必须去重新考虑一遍之前放置的元素。你可以看到,按这样的考虑方式,之前放置的元素会受到当前放置元素的影响

我们再来看看先放个子高的是怎么样的情况,还是考虑例子 [[5,0],[6,0],[7,0]],我们先放置元素 [7,0],放到第一个位置,[[7,0]] 然后放置 [6,0],还是放到第一个位置,[[6,0], [7,0]]。你可以看到,按这样的顺序,每当我们考虑一个元素,之前已经考虑过的元素都比当前元素大或是等于当前元素,不管把当前元素放到哪,当前元素的放置均不会对之前已经放置的元素造成影响而出问题,只需要按 “前面比他高的人数” 的值将当前元素插入到指定位置即可。

于是,我们可以采用第二种方式进行插入。这里有个小细节就是,如果两个人的身高相同,我们要先考虑放置人数少的。这一点也很好解释,对于身高相同的人,最后的答案肯定是,人数少的会插在人数多的前面,如果先插人数多的,那么再插人数少的就会对人数多的产生影响,而先插入人数少的就不会有这个问题。

综上所述,这道题的整体思路就是先排序后插入即可,但是你依然可以看到这其中的一些很微妙的思考过程。


参考代码

public int[][] reconstructQueue(int[][] people) {
    // 对输入数组基于身高排序(倒序)
    // 如果身高相同,基于人数排序(正序)
    Arrays.sort(people, new Comparator<int[]>() {
        @Override
        public int compare(int[] a, int[] b) {
            if (a[0] != b[0]) {
                return b[0] - a[0];
            }
            return a[1] - b[1];
        }
    });

    List<int[]> result = new LinkedList<>();
    // 基于人数将元素插入到指定位置即可
    for(int[] p : people){
        result.add(p[1], p);
    }

    return result.toArray(new int[people.length][]);
}

Review

Learn how to create custom bash commands in less than 4 minutes

在 Linux 类的操作系统上,我们平时会频繁的使用一些命令,比如 git add -A 或者是进入到某个目录中去,有些命令很长且不方便记忆。我们可以给命令起别名来提升我们的工作效率,你可以修改 ~/.bashrc 文件,在之中添加别名,但是这个文件通常是用来记录路径和一些系统配置的。我们可以新创建一个隐藏文件来专门记录别名,比如作者在 home 目录下创建了一个 .custom_aliases 的文件,起别名也很简单,你可以参考下面的例子:

# Version Control
alias gs="git status"
alias gd="git add -A"
alias gp="git push"

# cd
alias cdapp="cd ~/Documents/works/subfolder/application/app"
alias cdarts="cd ~/Documents/study_tech/arts/arts"

除此之外,我们还可以利用别名来一次性跑多个指令,其实也很简单,就是利用 && 符号,这个符号能够保证左边的命令在成功运行之后才会运行右边的命令,比如:

alias lazyman="git add . && git commit -a -m '$1' && git push -u origin master"

同样的事情也可以使用命令行的函数来做:

function lazyman() {
    git add .
    git commit -a -m "$1"
    git push -u origin master
}

给常见命令起别名这个技巧很简单,但是想要真真地让它提升你的工作效率,还是需要多尝试,多用,最好是养成创建并定期更新别名文件的习惯。


Tip

最近在使用阿里云的时候遇到一个问题。就是当你架起服务器后,你发现从自己的本机无法访问阿里云的服务器,每次发送请求都会超时。查看 文档,发现是需要配置安全组规则,但是令我不解的是即使配置好了规则,依然不奏效。

最后弄了很久才发现是,虽然创建好了安全组规则,但是我并没有保证 ECS 被放在安全组中,一般阿里云系统会自动帮你生成一个安全组,里面会有一些基本规则,这些基本规则我看了一下:

这其中是不包含 HTTP 默认的 80 端口的,因此还是需要自己单独创建规则,但是不管怎么样,一定要保证你创建的安全组或是规则一定要管理到你的实例


Share

这次来学习学习线段树,可以解决一些特定问题的数据结构

线段树基本操作解析