第 3 周 ARTS

一、Algorithms

113. Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.

Note: A leaf is a node with no children.

Example:

Given the below binary tree and sum = 22,

      5
     / \
    4   8
   /   / \
  11  13  4
 /  \    / \
7    2  5   1
Return:

[
   [5,4,11,2],
   [5,8,4,5]
]

第一版实现

思路: 通过 DFS 访问到每一个叶节点,将路径的每一个节点进行记录,并对 val 求和,到达叶节点后如果最终求和等于给定的 sum 则表示本条路径符合要求,将其添加到结果集中,返回上一层时将本层节点的 val 删除。

class Solution {

    private List<List<Integer>> paths = new ArrayList<>();
    private List<Integer> path = new ArrayList<>();
    private int sum = 0;

    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if (root == null) {
            return paths;
        }

        if (root.left == null && root.right == null && root.val == sum) {
            this.path.add(root.val);
            this.paths.add(this.path);
            return this.paths;
        }

        this.sum = sum;
        this.dfs(root, 0);
        return this.paths;
    }

    public void dfs(TreeNode node, int num) {
        num += node.val;
        this.path.add(node.val);
        if (node.left == null && node.right == null) {
            System.out.println(num);
            if (num == this.sum){
                List<Integer> newPath = new ArrayList<>(this.path);
                this.paths.add(newPath);
            }
        }
        if (node.left != null ){
            this.dfs(node.left, num);
        }
        if (node.right != null ) {
            this.dfs(node.right, num);
        }

        int index = this.path.size() - 1;
        this.path.remove(index);
    }

这是第一版的实现,测试没问题,但是性能很差,整个运行时间是 30 ms,beats 1%。原因是做了不必要的判断,在一开始判断 node.left == null && node.right == null 时就已经确定是否有子节点了,第一版的操作将判断次数变为了原来的 3 倍。修改后逻辑如下:

public void dfs(TreeNode node, int num) {
        # 添加节点为 null 情况下的判断
		 if (node == null) {
		     return;
		 }
        num += node.val;
        this.path.add(node.val);
        if (node.left == null && node.right == null) {
            System.out.println(num);
            if (num == this.sum){
                List<Integer> newPath = new ArrayList<>(this.path);
                this.paths.add(newPath);
            }
        }else {
            # 去掉了原来的两次多余的判断,性能瞬间提高
            this.dfs(node.left, num);
            this.dfs(node.right, num);
        }
        
       
        int index = this.path.size() - 1;
        this.path.remove(index);
    }

修改后的执行时间为 1ms,beats 100%。第一版虽然大致思路没错,但在实现时的思考的不够细致严谨,魔鬼在细节中呀。

另外自己的实现思路是 val 相加求和判断的。在 discuss 中看到了另外一种解法,是采用减的方式,从 root 节点开始,每遍历一个节点就从 sum 减去当前节点 val, 到叶节点时如果 node.value 等于 sum - val 后的值就表示满足条件。其代码如下:

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        List<Integer> current = new ArrayList<Integer>();
        pathSumHelper(root, sum, current, result);
        return result;
    }
    
    public void pathSumHelper(TreeNode root, int sum, List<Integer> current, List<List<Integer>> result) {
        if(root == null) return;
        
        current.add(new Integer(root.val));
        # 当前 sum == node.val 就表示满足
        if(root.left == null && root.right == null && root.val == sum) {
            result.add(new ArrayList<Integer>(current));
            current.remove(current.size()-1);  // don't forget to remove last element
            return;
        }
        else {
            # 传节点,并将 sum - node.val 传入
            pathSumHelper(root.left, sum-root.val, current, result);
            pathSumHelper(root.right, sum-root.val, current, result);
        }
        current.remove(current.size()-1); // here too, don't forget to remove last element
    }
}

二、Review

本周阅读了耗子哥专栏中推荐的两篇文章,

第一篇是Teach Yourself Programming in Ten Years

作者一开始提到了 《24小时精通 C++》 类似书籍的泛滥,让人觉得编程似乎是一件容易的事情,但包括编程在内的许多领域其实是需要大量刻意练习才可以达到一个非常高的水平。作者举了莫扎特和披头士的例子,他们在成名之前都有过很长时间的积累。关于时间说法很多,最常见的就是一万小时理论,作者采用了十年这个维度。作者提到了学习编程的几点:

  • 兴趣。兴趣是使你愿意投入近十年的时间来精进你的技能的动力。
  • 编程。最好的学习方式是动手去做,去刻意练习。不是反复做一些简单的已经明白的东西,而是在你的能力边界之外设定任务,然后去完成它。
  • 与其他程序员交流、读其他项目
  • 如果可以考虑读上四年大学,这样让你满足那些对学位职称有要求的岗位,当然,如果你是一个高手学位也没那么重要
  • 与其他程序员一起工作,如果你是一个团队中最优秀的,那么就发挥你的能力去推动整个项目的发展。如果你是最差的,你可以学习高手是怎么做的
  • 项目后的思考,思考如何优化你的项目,让接手你项目的人更容易维护
  • 至少学习六门语言,了解不同的编程范式。比如强调类抽象的 Java/C++、强调方法抽象的 List、强调并发的 Go 等。 这个的目的个人认为主要是为了了解不同的编程方式,不必非得熟练掌握语言,重点是语言背后的编程方式和设计思想。
  • 了解计算机系统,比如计算机操作内存、磁盘等命令的执行时间

作者最后强调,不要抱着 24 小时学会一门小时的心态学习,要坚持不断刻意练习,成长为一名卓越的工程师。

总体而言作者的观点还是告诫我们不要过于浮躁,踏踏实实的做好项目,不断给自己增加能力之外的挑战,逼迫自己不断的成长。难点在于道理谁都懂,但坚持下来的永远都是少数。更重要的是保持旺盛的求知欲自驱力,并始终拥有挑战未知的勇气,也可以理解为作者提到的 兴趣。


第二篇是
The Key To Accelerating Your Coding Skills

文章的主要目标是帮助开发人员度过一个拐点。当你度过这个拐点之后,就有了 “自给自足” 的能力。简单来说就是拥有了解决问题、独当一面的能力。

作者给出了如下几点内容:

1. 注重细节,不断试错

  • 仔细阅读文档,注意每行代码。因为每一个拼写错误、每一个配置错误都会导致难以察觉的问题
  • 仔细阅读 error message, 同时总结反思出错的原因和解决方案,而不是仅仅解决了问题
  • 解决实际问题。自信是在不断解决问题的过程中锻炼出来的,而不是读文档读出来的。

2. 摆正心态,戒骄戒躁

度过最开始的入门阶段后,你会进入一段时间比较长并且非常令人沮丧的阶段。该阶段你会遇到大量的问题和大量不懂的知识,甚至该阶段解决问题的速度要远慢于初始阶段。该阶段作者提到以下几点:

  • 不要试图学会所有东西。编程是一件终身学习的事情,知识无穷无尽,你不可能先学会知识,然后等着对应的问题发生。而是遇到问题,学习知识,解决问题。
  • 除了领域特定知识,你还需要学习 “Procedural knowledge”。直译过来是程序性知识。我理解为通用的编程、架构的范式、思想和自己的方法论。包括学习上的方法论,解决问题上的方法论。包括设计模式、编程范式、系统的架构类型及其背后的思想、trade-off 等。 这些帮助你在遇到新问题新知识时可以游刃有余的应对,这是比学会某一门语言、掌握某一个具体知识更重要的事情。
  • 每一天都要走出自己的舒适区,主动迎接挑战。

3. web 开发的两个拐点

  • 开发拐点: 你可以独立搭建一个基于数据库的 web 应用,实现基本的 CURD 功能。
  • 算法与数据拐点: 你能够写出排序算法;实现并反转链表;理解并利用栈、队列、树解决问题;使用递归、遍历等方法解决问题;

当你达到上述两个拐点,意味着你可以自由操作各种数据解决问题、并了解项目中的实现方式的性能优劣。

大学往往比较注重后者的算法与数据结构方面的学习。面试中也往往默认你已经度过了第一个拐点,重点考察算法和数据结构相关的内容。

4. 系统学习,文档优先

  • 尽量学习结构化系统的知识,并给自己设定问题去解决,可以适当设定超出学习范围之外的问题去解决
  • 优先读文档,教学视频通常无法照顾到各种细节,并且耗时更长

5. 劳逸结合,不断解惑

  • 整个过程是一个比较困难并且容易让人沮丧的过程。你需要全力以赴,但不要过度劳累。重在长跑
  • 该过程中你会遇到很多的挫折,自信心也会备受打击。最好的方式就是想清楚你遇到的每一个问题,在不断攻克问题的过程中,自信心随之建立,你会觉得,其实也没那么难
  • 有些东西可能会困扰你 5 分钟、5 个小时甚至 15 个小时。你在解决的过程备受煎熬,但一旦你解决了它,那种畅快感和自信便会滚滚而来

以上是对文章内容作了一个大致的梳理。最后作者还提到了如何确认自己已经到达拐点了呢?其实就是你有勇气和能力去解决更复杂的问题了。编程永远不是一劳永逸的事情,重要的是我们始终在不断进取的路上。

三、Technique

本周分享一个 通过 Python 快速实现局域网内文件传输的小技巧吧。

平时工作中如果遇到大文件的传输,通过网络传输是很慢的事情,可以通过 python 命令快速实现一个局域网,在命令行中执行如下命令:

# 在指定目录下执行,端口可以自己指定
➜  demo  python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...

这样,如果同事和我们连得是同一个无线网络,那么就可以通过访问我们的地址来访问对应的目录,从而实现快速下载:
图片

点击对应的文件就可以快速下载啦。

四. Share

最近在学习的时候如果在规划的时间内完不成制定的目标,焦虑感就会特别的强烈。从中途开始意识到任务开始完不成就产生焦虑,焦虑感又进一步影响进度,然后形成一个恶性循环。不知道有多少人和我一样有类似的体验。思考了一下归根结底还是高估了自己一天之内能做的事情,导致计划安排的过多。感觉这应该是一个普遍的现象,人们往往容易高估自己在短期内完成的事务量,而低估长期自己能做的事情。但具体到实践中,因为浮躁,琐事等各种意外因素导致时间的不够用,还是难免会陷入想在短期内尽快完成很多事情的境地。

这个问题结合编程学习又会更加的明显。相信大家都读了耗子哥新版的 《程序员练级攻略》系列的文章。里面耗子哥提供了大量的有价值的资料引导我们去学习。但当你真正进入到学习中去,你会发现那是一片茫茫的荒漠,你要长时间的付出精力来学习。这是个充满焦虑与挫折的过程。

关于这个问题,个人思考是主要有两点:

  • 心态层面: 首先要抱着 “无所谓” 的心态 认真 学习。简单来说就是给自己一个心理暗示,不要贪多,贵在进取。就像首富说的一个亿的小目标,就算实现不了,那一千万也是好的啊,一千万不行一百万也是好的呀。具体到学习,比如一本书,可能看不完,但看一章、看一页就是赚,就比一点也不看强。
  • 执行层面: 每天设定两三个比较小的,可以完成并且必须完成的任务。然后设置一些比较难的但是不必须完成的任务。比如我定两个目标,今天认真的读完一篇极客专栏文章,做一个简短的 ORID 笔记;学习 《算法(第四版)》的某一小节。前者简单必须做完,后者较难不必须完成,甚至如果很忙的话可以不完成,但前提是你真的在忙有意义的事情。这样降低心理预期之后,第二个多完成一点都是纯赚。

这是本周的 ARTS,S 的部分有好的方法的同学可以多多分享。学习,既要持之以恒,又要灵活变通追求效率,欢迎大家拍砖指点。

发布了46 篇原创文章 · 获赞 21 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/Ahri_J/article/details/105396550