Recursion

Recursion的概念就是把大問題化成小問題,最後在去解決小問題,解答會由小問題組成。

那要如何去把大問題拆成小問題呢?

我們可以先思考小問題是什麼?為什麼要有小問題?

  1. 大問題通常很難直接解決,但是如果把問題分解成小問題,就可以讓問題變得更容易處理。
  2. 小問題的解決方法通常比較簡單和直接,因此可以幫助我們理解和解決原問題。

例如,如果計算一個長度為n的數列的總和。這個問題可以分解成計算前n-1個元素的總和,再加上第n個元素的值。這樣,原本的大問題就就可以分為為兩個小問題:1. 計算前n-1個元素的總和,2. 計算第n個元素的值。這兩個子問題可以遞迴地解決,最後得到大問題的解答。

在這個例子中,小問題是計算一個元素的值,大問題是算整個數列的總和。通過分解成小問題,我們可以更容易地理解和解決原問題。

以Tree來說,左子樹和右子樹的結果是小問題的答案,我們沒有辦法在root這個點就知道下面的問題,但每個node都會期待小問題會給我解答,所以一直不停把問題丟下去拆成小問題,結果會從result傳回來,拿回傳值做判斷。

Bottom-up 和 Top-down

在前幾個tree章節,我們也用過Recursion的解法,Recursion又分成Bottom-up 和 Top-down 方法,而Top-down 或Bottom-up 內又有許多解法,例如 Top-down 方法內又分成Backtracking 回溯或是其他解法。這裡就先以Top-down 和Bottom-up 為中心來說明。

  • Bottom-up

Bottom-up 方法通常要先思考leaf 終止條件,從子問題開始,預期左右子樹兩邊會傳回什麼,思考怎樣形成root的答案,最後再去防一下是空的怎辦?沒有左邊或右邊怎麼辦?就這樣一步步去構建解決方案,直到求解大問題的最優解。每個子問題的最優解都被計算一次,並存儲在一個表格或數組中。最後,這些子問題的解決方案被組合成一個整體解決方案,解決大問題的最優解。

Bottom-up 方法的keypoints是

  1. 要先思考leaf 終止條件
  2. 要去思考如何把變數由leaf往上帶到root
  3. 如何處理每個節點,考慮每個節點的特點,並且適當地處理各種可能的情況(是空的怎辦?沒有左邊或右邊怎麼辦?)

以下為Bottom-up 的一個例子

509. Fibonacci Number

  • Top-down

Top-down 方法則是從大問題開始,遞歸地解決其子問題,直到求解最小的子問題為止。這種方法比較像正常人的思考方式,但CODE會比較常比較醜,目前執行的行數和變數會存在MemoryStack, 所以空間複雜會使O(d) ,d是樹的深度,而時間複雜會是O(n),n 是樹的節點數。

Top-down 方法的keypoints是

  1. 要去思考如何把變數由根往下帶
  2. 用global variable把變數存起來
  3. 如何處理每個節點,考慮每個節點的特點,並且適當地處理各種可能的情況(是空的怎辦?沒有左邊或右邊怎麼辦?)

以下為Top-down 的一個例子

Top-down 的Recursion:計算n的階乘

def factorialTopDown(n: int) -> int:
    dict = {}  # 使用字典記錄已經計算過的n的階乘值

    def recr(n):
        if n in dict:
            return dict[n]

        if n == 1:
            dict[n] = 1  # 1的階乘為1
        else:
           dict[n] = n * recr(n - 1)  # 計算n的階乘

        return dict[n]

    return recr(n)

112. Path Sum

Bottom-up 和 Top-down 哪個比較好呢?

Bottom-up會比Top-down 來的好,因為有方法加速且不需要使用 Memoize 技術的情況下,就已經可以有效地避免重複計算。

Tail recursion

Tail recursion是一種特殊的遞歸形式,它在函數的最後一步(最後一行)才有tail recursion,且一定是bottom-up方法,關於空間複雜度,只有在C/C++會幫你優化成O(1)

509. Fibonacci Number

Pass by reference & pass by value

參考文章 : Python 是 Pass By Value, Pass by Reference, 還是 Pass by Sharing?

Pass by reference(按參考傳遞)和 pass by value(按值傳遞)是兩種不同的參數傳遞方式。

Pass by value 指的是將參數的值複製一份,然後將這份複製的value傳遞給函數。在函數內部對參數進行修改不會影響原來的value。這種方式適用於immutable(不可變類型)的參數,例如 int、float、str 等。因為這些類型的value不可被修改,所以比較不會出現問題。

Pass by reference 指的將參數的引用(內存地址)傳遞給函數。在函數內部對參數進行修改會影響原來的值。這種方式適用於mutable(可變類型)的參數,例如 list、dict、set、tuple 等。這些類型的值可以被修改,所以可能會出現問題。