Arts 第六十六周(7/13 ~7/19)

191 阅读12分钟

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

Algorithm

LC 222. Count Complete Tree Nodes

题目解析

输入是一个 完全二叉树,让你求出树上的节点个数。这里先说一下完全二叉树的定义。如果我们从根节点的那一层开始,按从左到右,从上到下的顺序放置节点,直到把所有节点放置完毕,那么这个树就是完全二叉树,例如:

    1
   / \
  2   3
 / \  /
4  5 6

很容易发现,完全二叉树有一个性质是:除了后两层,其余层上的节点都有左右两个子树。而且除叶子节点以外,其余节点的子节点只会有两种情况:

  • 既有左节点,又有右节点
  • 只有左节点

当然,这道题目最直接的解法就是遍历一遍树,然后得出节点个数。这种解法对任何二叉树都奏效,因为要遍历到所有的节点,时间复杂度就会是 O(n)。但仔细想一想,这种解法并不是这道题想要考察的点,毕竟我们没有很好地利用题目给出的树的特殊性质。

其实这道题目最纠结的地方在于,我们并不知道最后一层被填了多少个节点。如果知道了的话,这道题用一个数学式子就可以算出来:

节点数 = 最后一层的节点数 + 2^(h-1) - 1

这里的 h 表示的是树的层数。

无论如何,有一点我们可以确定的是,每一层的节点是从左向右填的,如果我们把这个树从根节点处拆成左子树和右子树,那么说不定拆出来的两个树会存在满二叉树,满二叉树是完全二叉树的一个特殊情况,它的最后一层是被填满的,比如:

    1                  1
   / \                左  右
  2   3              2    3
 / \  /             / \  /
4  5 6             4  5 6

可以看到,上面拆出来的左子树就是一个满二叉树,它的节点树就可以用式子直接算出来。那么具体怎么实现呢?我们可以分别找到左子树的最左边的叶子节点,还有右子树的最左边的叶子节点,然后分别看这两个节点的层数,会存在以下两种情况:

  • 所在层数相同,表示左子树最后一行是被填满的,我们可以直接计算得到左子树的节点个数
  • 所在层数不相同,表示右子树最后一行是被填满的,我们可以直接计算得到右子树的节点个数

每次切分,我们都可以将树一分为二,这样时间复杂度就是 O(lgn)。最后来总结一下这道题目,在解二叉树相关的题目的时候,拆分的思想很重要,二叉树比较直观,只能拆成左子树和右子树。拆完后再看子树上面会不会有一些不同的性质。


参考代码

func countNodes(root *TreeNode) int {
    if root == nil {
        return 0
    }

    // 分别计算左子树和右子树的最左边的节点所在的层数
    left, right := leftHeight(root.Left), leftHeight(root.Right)

    // 如果所在层数相同,可以确定左子树最后一层被填满
    // 这样左子树的节点个数可以通过公式计算出来,右子树继续遍历
    if left == right {
        // 1 << left 表示的是左子树节点的个数加上根节点
        return countNodes(root.Right) + (1 << left)
    }

    // 如果所在层数不相同,也就是左子树可能没有被填满
    // 但是此时可以确定的是,右子树是填满了的,因此可以直接计算出右子树的节点个数
    return countNodes(root.Left) + (1 << right)
}

func leftHeight(root *TreeNode) int {
    pointer, height := root, 0
    for pointer != nil {
        pointer = pointer.Left
        height++
    }

    return height
}

Review

How to Master Any New Skill

Medium 上的一篇文章,读了感觉受益匪浅。

文章想要解决的问题

生活在当今互联网时代,你会发现之前很火的职业或者是技术慢慢地被取代,甚至是消失。比如之前的工厂流水线上的操作工人已经被自动化的机器所取代;再早些时候,手机完全是一个奢侈品,但现如今智能手机已经普通的不能再普通的设备。随着科技的发展,人们的生活变得越来越便利。但是,反过来看,如果你要在这个社会上立足,有一个不错的职业生涯,你需要学的东西也是越来越多。

作为一个程序员,要学的东西多,这个很能感同身受。那么这里问题就是,如何才能更好地掌握新的技术让自己不被时代所淘汰呢? 文章讲述了一些意识层面,或者说是认知层面的东西,这有助于我们认识到前方的路。我们说选择比努力更重要,这篇文章在某种程度上就是在帮助你做出更好的选择。

掌握新技能的六大策略

  • 理智的选择自己要学的东西

    在你学一项技能或者是技术时,第一步需要思考的就是 “我要学什么”。在很多地方都提到,真正掌握一项技能需要 10000 小时。因为我们的时间有限,所以我们不可能做到各个领域都精通。那么做一个好的选择就极为重要。

    那怎么选择要学的东西呢?文章给的建议是 从自身出发,想想看哪些东西能够帮助你实现你最终的目标。然后,也是最重要的,不管学习的结果或者说是成果如何,你都能很享受学习的这一个过程。基于上面提到的这两点,如果一个技能或者是技术,对你实现你当前的人生目标有帮助,并且在学习的过程中你并不会感到有压力。那么就说明你做出的是对的选择。

    你可能会说,现在我也不知道我感兴趣的是什么,甚至说我也不清楚我的人生目标是什么。那怎么办呢?的确,很多时候我们只能依附于环境,环境一变导致我们被迫做出很多改变,换句话说,一个东西的 “钱途” 决定了我们的选择。在这种情况下,不妨试着去做一些事情,了解一些东西,因为喜欢和爱好不会凭空产生。等到你接触了足够多的事物之后,可能你会有和当初不一样的思考。总之,空想,空抱怨肯定是不行的,你需要积极地行动起来。

  • 放下你的自尊

    在学习一项新的技能的过程中,遭遇挫折是在所难免的。有些时候,为了学好一项技能,我们甚至都要改变之前存在多年的习惯。我们的自尊心很多时候是不接受批评,自尊心也会让我们认识到我们已经足够好了,不需要学习更多的东西。但这恰恰就是问题所在,不要忘了,你不知道你不知道的东西。如果不能做到放下自尊心,虚心地学习,那么到头来只能是井底之蛙。

    往往你学的越多,会发现自己知道的越少,更加需要学习。

  • 学习规则,为了能够知道如何打破他们

    学习有三个阶段,从低到高分别是:学习规则,学习策略,学习如何打破策略。这里我就拿编程来举例,首先学习编程我们需要知道如何让程序运行起来,如何用编程解决实际的问题,用的多了,你就会熟悉很多规则,比如 HTTP 头是如何定义的,TCP 是怎么和 HTTP 进行连接的,操作系统线程池是怎样工作的,面向对象编程是怎么玩的。。。

    学了这些就结束了吗?其实这只是第一阶段,这一阶段我们需要去了解很多我们并不熟悉的东西。在刚开始的时候,我们都会有一种知其然,不知其所以然的感觉。在这一阶段,我们可以熟练的运用某个工具或者是技术帮助我们解决问题,但我们其实并不了解这个工具或者是技术为什么要这么设计

    第二阶段就是要学习一些思想层面的东西,比如怎么样的架构设计才能让程序运行的更加稳定,网络传输为什么要设计成这样的格式,操作系统的设计解决了哪些问题。在这个阶段,你会更加着重于思考 “为什么”,而不是 “是什么”。在这一阶段时间长了,你会更加了解一些技术的背景,和知识的来龙去脉。这样有助于把你之前学的东西串起来,形成一个连贯的整体。但这还不是主要的,主要的是,你能够站在技术设计者的立场和背景去思考问题。

    第三阶段就是要打破策略,这一阶段可以说是最具有产出和影响力的阶段。当你在某一领域的积累和阅历到了一定程度的时候,你或许能够看到大多数人看不到的问题。如果你尝试着去解决,到最后你会设计出一个更好的技术,推动着这个领域不断向前。当然,能到这一阶段的人也是寥寥无几,有时甚至还需要有一定的运气。

    不管怎样,明白你当前所在的阶段,知道你当前的目标才是最重要的。这个问题的答案只有你自己才知道。

  • 实践胜过理论

    有些时候,我们认为自己已经掌握了某项技术或者是能力。但是不真正的实践一番是很难发现问题的,因此就会产生 “想象中的自己” 和 “现实中的自己”。YouTube 上的这个 视频 就形象地说明了这个问题。

    千万不要觉得自己会了就可以很好地应用,努力了就一定有回报。要做好失败的准备。但很多时候,失败并不是一件坏事,失败意味着这中间仍旧存在问题,我们需要做的是找到问题并想办法解决才能变得更好。受了多年应试教育的我们,可能潜意识里会觉得在测试当中犯错导致得分低是不好的。但是在生活中,犯错才能为你学习提供很好的机会和方向。

    想掌握好一门技艺,刻意联系,有反馈地重复才是最关键的,这里没有捷径。

  • 学习一些原则性的东西

    这里列出来了一些需要知道的认知层面的东西,这些东西适用于所有的职业和领域的学习:

    • 概率思维:学会如何在信息不足的情况下做好决策
    • 成本效益分析
    • 实验的方法
    • 次级效应
    • 博弈论
    • 心理学
    • 逻辑学
  • 在过程中衡量进步,而不是看结果

    如果是我们仅仅用最后的结果来衡量我们所做的事情,那我们就会陷入到误区中。其实很多时候,结果并不一定能反应我们的能力。就好比创业,大部分初创公司到最后都没能上市,那你能说没有成功的创业公司的经验都不值得借鉴吗?

    另外,突然而来的成功也有可能让你停下前进的脚步。如果对待任何事情,我们能有一个初学者的心态,那是最好不过的。


Tip

这周使用了 Ant Design Pro,它是阿里旗下的蚂蚁金服开发的基于 Ant Design UI 的一个布局设计框架。给我的感觉是,这个框架节省了很多前端开发的时间。对于我这种刚接触前端不久的人来说,但又想快速做出一个像样的页面,这个框架再适合不过了,这里简单记录一下安装、使用以及介绍一下核心模块。

  • 安装

    >$ npm create umi
    

    安装的过程中,你可以选择 4.0 和 5.0 两个版本。其中 5.0 版本仅仅支持 TypeScript。在 4.0 版本下,你可以选择 JavaScript 或者是 TypeScript,由于对 TypeScript 不太熟,所以我选择 4.0 下的 JavaScript。

    安装完成后,就是常规地,安装对应依赖:

    >$ npm install
    
  • 启动

    >$ npm start
    

    启动后,在本地的 8000 端口就可以看到前端页面

除了 Ant Design 之外,Ant Design Pro 主要是基于 UmiJSDva 这两个前端框架。其中 UmiJS 是一个前端的路由框架。在其官网的介绍中可以看出,这个框架其实是一个 React 工具集,就是将我们平时用的一些包,比如 react-routerreact-router-dom 进行打包整理,提供一个更加人性化的结构,便于快速上手。Dva 主要是基于 redux的一个轻量级的前端框架。

UmiJS 和 Dva 在阿里内部被广泛使用,其功能是被验证过的,所以可以放心使用。

从几天的接触和使用中来看,感觉有一个框架确实省时省力了不少。但是仅仅是依附于框架解决问题是远远不够的,当前看来,还有两件事情需要去做:TypeScript剖析 Ant Design Pro 的源码设计和文件架构


Share

这次分享的是两篇关于职场焦虑的两篇文章

为何有的职业后期不给力

焦虑:程序员怎样才能越干越给力?

往往现实与我们期望的不相符,我们就会产生焦虑。此时你可以做的有两种选择,接受现实,承认自己就是当下这样一个状态,不对生活抱有更高的期待,也就不存在焦虑了。相反,另一个选择就是行动起来,有焦虑说明有问题没有解决,那么就可以根据自身的情况调整自己的短期目标。总之,最后的结果如何先不去关心,努力让自己沉浸到做事情本身上来,而不去过多思考做这件事到底能给自己带来什么样的好处。这样有助于把自己的注意力放到更有意义的事情上去,而不是毫无根据地拿自己和别人进行比较。