ARTS 第4周| LeetCode 1143 最长上升子序列| 21天能学会编程吗| Go defer 的用法

ARTS

ARTS 是陈浩(网名左耳朵耗子)在极客时间专栏里发起的一个活动,目的是经过分享的方式来坚持学习。html

每人每周写一个 ARTS:Algorithm 是一道算法题,Review 是读一篇英文文章,Technique/Tips 是分享一个小技术,Share 是分享一个观点。

本周内容

这周的 ARTS 你将看到:程序员

  1. 动态规划典型中的典型,最长上升子序列。
  2. 你对编程的热爱都用十年吗?
  3. 这些 defer 的坑让你大呼卧槽。
  4. 本周没有灵光一闪。

Algorithm

本周的算法题是经典题目:最长上升子序列。面试

LeetCode 1143. Longest Increasing Subsequence算法

这道题便是经典的动态规划题,也是经典面试题。可是由于前一段时间作了太多回溯相关的题,因此这道题我偏要用 DFS 来 AC,但没想到这真的挺坑。话很少说,直接贴 DFS 代码。shell

//  这是一次 DFS 的超低空飞行,危险刺激可是能 AC
func lengthOfLIS_DFS(nums []int) int {
    var dfs func(s, prev, count int) int
    // 直接递归会超时,必须加记忆
    // key 表示 nums 字符起始位置
    // value 表示从 key 表明的位置开始到 nums 结尾 LIS 的长度增量
    mem := make(map[int]int, 0)
    // s 正在处理的子串起始下标
    // prev 递归树中上一个层级已构成的上升序列最大值(最后一个元素值)
    // count nums[s] 加入到递归分支的 LIS 以后 LIS 的长度
    // 这样之因此能够记忆中间结果是由于
    // 不一样的递归求解 LIS 的过程可能会通过相同的 nums[s] 
    // 只要当前到达的 nums[s] 相同的话,后续能将上升序列长度增长多少
    // 彻底取取决于 nums[s] 以后的子数组构成
    // 这种记忆方式已经无限接近 DP 了
    dfs = func(s, prev, count int) int {
        if _, ok := mem[s]; ok {
            return count+mem[s]
        }
        var ret int
        flag := false
        for i := s; i < len(nums); i++ {
            if nums[i] > prev {
                flag = true
                ret = max(dfs(i+1, nums[i], count+1), ret)
            }
        }
        if !flag {
            ret = count-1
        }
        mem[s] = ret - count
        return ret
    }
    return dfs(0, -1<<31, 1)
}

func max(a, b int) int {
    if a < b {
        return b
    }
    return a
}

DFS 代码没有变量 mem 来记忆中间结果的话,确定会超时,可是考虑若是作这个记忆的确花了不少时间。思考过程都在注释里了,下面是枯燥无味的 DP 解法。编程

// DP 就是这么枯燥且无味,毫无优化就 faster than 71.14%
func lengthOfLIS(nums []int) int {
    // dp[i] 表示截止到 nums[i] 时 LIS 的长度
    // 须要遍历一遍 dp 找到最大值
    dp := make([]int, len(nums))
    // base case 看起来很像废话,但 LIS 的长度确实最少是 1
    for i := 0; i < len(dp); i++ {
        dp[i] = 1
    }    
    var ans int
    for i := 0; i < len(nums); i++ {
        for j := 0; j < i; j++ {
            if nums[i] > nums[j] {
                dp[i] = max(dp[i], dp[j]+1)
            }
        }
        ans = max(ans, dp[i])
    }
    return ans
}

这道题算是 DP 典型题中的典型题,因此不管如何也要理解呦。数组

Review 文章推荐

这周回一下来好像没有看任何一篇值得拿出来 Review 的好的纯技术英文文章,可是由于最近看了很多技术人的成长和学习的实践方式和方法论相关的文章,这周就回顾一下这个话题相关的几篇文章吧。工具

首先,是这一篇Teach Yourself Programming in Ten Years,直译过来就是“花十年时间自学编程”。做者对这些年“21天学会XX”这种书籍的流程发表了本身的见解。做者强调21天只能让你了解一个技术领域,可是若是精通或者成为专家,多是要十年才行。学习

而后,是这一篇The Greatest Developer Fallacy Or The Wisest Words You’ll Ever Hear?. 文章的标题中所说的多是至理名言也多是谬论的话,其实就是你们日常所说的「我会在真正用到的时候在学习这项技能」。这句话初看起来闪烁着实用主义的光芒,但实际上并不彻底可行。由于不少问题并非优雅地出现,常常是经过各类故障瓶颈被发现,跟你用来处理问题的时间并很少。亦或是在平时的开发中,若是你「并不知道」有某项技术工具或者算法可以快速解决一个让你焦头烂额问题,那么你极可能会事倍功半。做者在文中有个比方很恰当:“等我有了钱我再学理财”,因此到底先有鸡仍是先有蛋?我想应该是先学会理财才能提高财富增加的速度,先了解技术领域才能在须要的时候知道该用什么技术解决问题。在陈浩的这篇中文翻译中有一个读者留言写的很是好。优化

我以为真正的问题不是“Learn it when I need it”,而是作不到“Do one thing and do it well”:当须要的时候,没有深度地学习,而是浅尝辄止。... 从Google到SO,惊喜地发现一段看似合适的代码,侥幸地试一试,居然成功了,再看看IE,效果还过得去,那就这样吧。这更算是“Copy it when I need it”.

因此说,十年或者说十万小时是某种客观存在的积累过程,不存在任何绝对意义上的捷径,若是有的话那只多是让你一直坚持下去的热爱了。这样说来兴趣可能不只仅带你走进一个领域,更是决定了你能走多远。务虚说了这么多,下面是几篇务实的文章。说归说,最后践行起来仍是任重道远的。
陈浩的程序员练级攻略-2018版 以及曹春晖的文章工程师应该怎么学习 推荐给你。

Tip 编程技巧

这周的技巧延续了上周的阅读内容,搜索 defer 原理的时候找到了一些挺有意思的总结,比这篇一些让你惊呼“卧槽”的 defer 技巧。主要是一些你可能很难想到的 defer 隐形坑位,可能平时开发不会有人这么写(拐弯抹角给本身挖坑),但做为一些思惟方式训练仍是很好的,能够更深入的理解 defer. 嗷对了,这是做者的一个列文章,但都很简短,花一个小时就能够看完。

Share 灵光一闪

本周没有灵光一闪,但愿下周能闪一下。以上。

相关文章
相关标签/搜索