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 靈光一閃

本週沒有靈光一閃,但願下週能閃一下。以上。

相關文章
相關標籤/搜索