主頁 >  其他 > 一文學懂遞回和動態規劃

一文學懂遞回和動態規劃

2020-09-21 09:08:21 其他

前言

大家好,這里是《齊姐聊演算法》系列之遞回和 DP 問題,

遞回,是一個非常重要的概念,也是面試中非常喜歡考的,因為它不但能考察一個程式員的演算法功底,還能很好的考察對時間空間復雜度的理解和分析,

本文只講一題,也是幾乎所有演算法書講遞回的第一題,但力爭講出花來,在這里分享四點不一樣的角度,讓你有不同的識訓,

  • 時空復雜度的詳細分析
  • 識別并簡化遞回程序中的重復運算
  • 披上羊皮的狼
  • 適當炫技助我拿到第一份作業

演算法思路

大家都知道,一個方法自己呼叫自己就是遞回,沒錯,但這只是理解遞回的最表層的理解,

那么遞回的實質是什么?

答:遞回的實質是能夠把一個大問題分解成比它小點的問題,然后我們拿到了小問題的解,就可以用小問題的解去構造大問題的解,

那小問題的解是如何得到的?

答:用再小一號的問題的解構造出來的,小到不能再小的時候就是到了零號問題的時候,也就是 base case 了,

那么總結一下遞回的三個步驟:

Base case:就是遞回的零號問題,也是遞回的終點,走到最小的那個問題,能夠直接給出結果,不必再往下走了,否則,就會成死回圈;

拆解:每一層的問題都要比上一層的小,不斷縮小問題的 size,才能從大到小到 base case;

組合:得到了小問題的解,還要知道如何才能構造出大問題的解,

所以每道遞回題,我們按照這三個步驟來分析,把這三個問題搞清楚,代碼就很容易寫了,

斐波那契數列

這題雖是老生常談了,但相信我這里分享的一定會讓你有其他識訓,

題目描述

斐波那契數列是一位意大利的數學家,他閑著沒事去研究兔子繁殖的程序,研究著就發現,可以寫成這么一個序列:1,1,2,3,5,8,13,21… 也就是每個數等于它前兩個數之和,那么給你第 n 個數,問 F(n) 是多少,

決議

用數學公式表示很簡單:

$$f(n) = f(n-1) + f(n-2)$$

代碼也很簡單,用我們剛總結的三步:

  • base case: f(0) = 0, f(1) = 1.
  • 分解:f(n-1), f(n-2)
  • 組合:f(n) = f(n-1) + f(n-2)

那么寫出來就是:

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        } else if (N == 1) {
            return 1;
        }
        return fib(N-1) + fib(N-2);
    }
}

但是這種解法 Leetcode 給出的速度經驗只比 15% 的答案快,因為,它的時間復雜度實在是太高了!

程序分析

這就是我想分享的第一點,如何去分析遞回的程序,

首先我們把這顆 Recursion Tree 畫出來,比如我們把 F(5) 的遞回樹畫出來:

那實際的執行路線是怎樣的?

首先是沿著最左邊這條線一路到底:F(5) → F(4) → F(3) → F(2) → F(1),好了終于有個 base case 可以回傳 F(1) = 1 了,然后回傳到 F(2) 這一層,再往下走,就是 F(0),又觸底反彈,回到 F(2),得到 F(2) = 1+0 =1 的結果,把這個結果回傳給 F(3),然后再到 F(1),拿到結果后再回傳 F(3) 得到 F(3) = 左 + 右 = 2,再把這個結果返上去...

這種方式本質上是由我們計算機的馮諾伊曼體系造就的,目前一個 CPU 一個核在某一時間只能執行一條指令,所以不能 F(3) 和 F(4) 一起進行了,一定是先執行了 F(4) (本代碼把 fib(N-1) 放在前面),再去執行 F(3).

我們在 IDE 里 debug 就可以看到堆疊里面的情況:這里確實是先走的最左邊這條線路,一共有 5 層,然后再一層層往上回傳,

沒看懂的小伙伴可以看視頻講解哦~

完整版視頻還請大家移步 B 站,搜索「田小齊」「遞回」,即可看到我的視頻講解,

時間復雜度分析

如何評價一個演算法的好壞?

很多問題都有多種解法,畢竟條條大路通羅馬,但如何評價每種方法的優劣,我們一般是用大 O 運算式來衡量時間和空間復雜度,

時間復雜度:隨著自變數的增長,所需時間的增長情況,

這里大 O 表示的是一個演算法在 worst case 的表現情況,這就是我們最關心的,不然春運搶車票的時候系統 hold 不住了,你跟我說這個演算法很優秀?

當然還有其他衡量時間和空間的方式,比如

Theta: 描述的是 tight bound
Omega(n): 這個描述的是 best case,最好的情況,沒啥意義

這也給我們了些許啟發,不要說你平時表現有多好,沒有意義;面試衡量的是你在 worst case 的水平;不要說面試沒有發揮出你的真實水平,扎心的是那就是我們的真實水平,

那對于這個題來說,時間復雜度是多少呢?

答:因為我們每個節點都走了一遍,所以是把所有節點的時間加起來就是總的時間,

在這里,我們在每個節點上做的事情就是相加求和,是 O(1) 的操作,且每個節點的時間都是一樣的,所以:

總時間 = 節點個數 * 每個節點的時間

那就變成了求節點個數的數學題:

在 N = 5 時,

最上面一層有1個節點,
第二層 2 個,
第三層 4 個,
第四層 8 個,
第五層 16 個,如果填滿的話,想象成一顆很大的樹:)

這里就不要在意這個沒填滿的地方了,肯定是會有差這么幾個 node,但是大 O 表達的時間復雜度我們剛說過了,求的是 worst case.

那么總的節點數就是:
1 + 2 + 4 + 8 + 16

這就是一個等比數列求和了,當然你可以用數學公式來算,但還有個小技巧可以幫助你快速計算:

其實前面每一層的節點相加起來的個數都不會超過最后一層的節點的個數,總的節點數最多也就是最后一層節點數 * 2,然后在大 O 的時間復雜度里面常數項也是無所謂的,所以這個總的時間復雜度就是:

最后一層節點的個數:2^n

空間復雜度分析

一般書上寫的空間復雜度是指:

演算法運行期間所需占用的所有記憶體空間

但是在公司里大家常用的,也是面試時問的指的是
Auxiliary space complexity

運行演算法時所需占用的額外空間,

舉例說明區別:比如結果讓你輸出一個長度為 n 的陣列,那么這 O(n) 的空間是不算在演算法的空間復雜度里的,因為這個空間是跑不掉的,不是取決于你的演算法的,

那空間復雜度怎么分析呢?

我們剛剛說到了馮諾伊曼體系,從圖中也很容易看出來,是最左邊這條路線占用 stack 的空間最多,一直不斷的壓堆疊,也就是從 5 到 4 到 3 到 2 一直壓到 1,才到 base case 回傳,每個節點占用的空間復雜度是 O(1),所以加起來總的空間復雜度就是 O(n).

我在上面??的視頻里也提到了,不懂的同學往上翻看視頻哦~

優化演算法

那我們就想了,為什么這么一個簡簡單單的運算竟然要指數級的時間復雜度?到底是為什么讓時間如此之大,

那也不難看出來,在這棵 Recursion Tree 里,有太多的重復計算了,

比如一個 F(2) 在這里都被計算了 3 次,F(3) 被計算了 2 次,每次還都要再重新算,這不就是狗熊掰棒子嗎,真的是一把辛酸淚,

那找到了原因之后,為了解決這種重復計算,計算機采用的方法其實和我們人類是一樣的:記筆記

對很多職業來說,比如醫生、律師、以及我們工程師,為什么越老經驗值錢?因為我們見得多積累的多,下次再遇到類似的問題時,能夠很快的給出解決方案,哪怕一時解決不了,也避免了一些盲目的試錯,我們會站在過去的高度不斷進步,而不是每次都從零開始,

回到優化演算法上來,那計算機如何記筆記呢?

我們要想求 F(n),無非也就是要
記錄 F(0) ~ F(n-1) 的值
那選取一個合適的資料結構來存盤就好了,

那這里很明顯了,用一個陣列來存:

Index 0 1 2 3 4 5
F(n) 0 1 1 2 3 5

那有了這個 cheat sheet,我們就可以從前到后得到結果了,這樣每一個點就只算了一遍,用一個 for loop 就可以寫出來,代碼也非常簡單,

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        }
        if (N== 1) {
            return 1;
        }
        int[] notes = new int[N+1];
        notes[0] = 0;
        notes[1] = 1;
        for(int i = 2; i <= N; i++) {
            notes[i] = notes[i-1] + notes[i-2];
        }
        return notes[N];
    }
}

這個速度就是 100% 了~

但是我們可以看到,空間應該還有優化的余地,

那仔細想想,其實我們記筆記的時候需要記錄這么多嗎?需要從幼兒園到小學到初中到高中的筆記都留著嗎?

那其實每項的計算只取決于它前面的兩項,所以只用保留這兩個就好了,

那我們可以用一個長度為 2 的陣列來計算,或者就用 2 個變數,

更新代碼:

class Solution {
    public int fib(int N) {
        int a = 0;
        int b = 1;
        if(N == 0) {
            return a;
        }
        if(N == 1) {
            return b;
        }
        for(int i = 2; i <= N; i++) {
            int tmp = a + b;
            a = b;
            b = tmp;
        }
        return b;
    }
}

這樣我們就把空間復雜度優化到了 O(1),時間復雜度和用陣列記錄一樣都是 O(n).

這種方法其實就是動態規劃 Dynamic Programming,寫出來的代碼非常簡單,

那我們比較一下 Recursion 和 DP:

Recursion 是從大到小,層層分解,直到 base case 分解不了了再組合回傳上去;
DP 是從小到大,記好筆記,不斷進步,

也就是 Recursion + Cache = DP

如何記錄這個筆記,如何高效的記筆記,這是 DP 的難點,

有人說 DP 是拿空間換時間,但我不這么認為,這道題就是一個很好的例證,

在用遞回解題時,我們可以看到,空間是 O(n) 在堆疊上的,但是用 DP 我們可以把空間優化到 O(1),DP 可以做到時間空間的雙重優化,

其實呢,斐波那契數列在現實生活中也有很多應用,

比如在我司以及很多大公司里,每個任務要給分值,1分表示大概需要花1天時間完成,然后分值只有1,2,3,5,8這5種,(如果有大于8分的任務,就需要把它 break down 成8分以內的,以便大家在兩周內能完成,)
因為任務是永遠做不完的而每個人的時間是有限的,所以每次小組會開會,挑出最重要的任務讓大家來做,然后每個人根據自己的 available 的天數去 pick up 相應的任務,

披著羊皮的狼

那有同學可能會想,這題這么簡單,這都 2020 年了,面試還會考么?

答:真的會,

只是不能以這么直白的方式給你了,

比如很有名的爬樓梯問題:

一個 N 階的樓梯,每次能走一層或者兩層,問一共有多少種走法,

這個題這么想:

站在當前位置,只能是從前一層,或者前兩層上來的,所以 f(n) = f(n-1) + f(n-2).

這題是我當年面試時真實被問的,那時我還在寫 python,為了炫技,還用了lambda function:

f = lambda n: 1 if n in (1, 2) else f(n-1) + f(n-2)

遞回的寫法時間復雜度太高,所以又寫了一個 for loop 的版本

def fib(n)
  a, b = 1, 1
  for i in range(n-1):
	a, b = b, a+b
  return a 

然后還寫了個 caching 的方法:

def cache(f):
	memo = {}
	def helper(x):
		if x not in memo:
			memo[x] = f(x)
		return memo[x]
	return helper
@cache
def fibR(n):
	if n==1 or n==2: return 1
	return fibR(n-1) + fibR(n-2)

還順便和面試官聊了下 tail recursion:

tail recursion 尾遞回:就是遞回的這句話是整個方法的最后一句話,

那這個有什么特別之處呢?

尾遞回的特點就是我們可以很容易的把它轉成 iterative 的寫法,當然有些智能的編譯器會自動幫我們做了(不是說顯性的轉化,而是在運行時按照 iterative 的方式去運行,實際消耗的空間是 O(1))

那為什么呢?

因為回來的時候不需要 backtrack,遞回這里就是最后一步了,不需要再往上一層返值,

def fib(n, a=0, b=1):
	if n==0: return a
	  if n==1: return b
	return fib(n-1, b, a+b)

最終,拿出了我的殺手锏:lambda and reduce

fibRe = lambda n: reduce(lambda x, n: [x[1], x[0]+x[1]], range(n), [0, 1])

看到面試官滿意的表情后,就開始繼續深入的聊了...

所以說,不要以為它簡單,同一道題可以用七八種方法來解,分析好每個方法的優缺點,引申到你可以引申的地方,展示自己扎實的基本功,這場面試其實就是你 show off 的機會,這樣才能騙過面試官啊~lol

如果你喜歡這篇文章,記得給我點贊留言哦~你們的支持和認可,就是我創作的最大動力,我們下篇文章見!

我是小齊,紐約程式媛,終生學習者,每天晚上 9 點,云自習室里不見不散!

更多干貨文章見我的 Github: https://github.com/xiaoqi6666/NYCSDE

前言

大家好,這里是《齊姐聊演算法》系列之遞回和 DP 問題,

遞回,是一個非常重要的概念,也是面試中非常喜歡考的,因為它不但能考察一個程式員的演算法功底,還能很好的考察對時間空間復雜度的理解和分析,

本文只講一題,也是幾乎所有演算法書講遞回的第一題,但力爭講出花來,在這里分享四點不一樣的角度,讓你有不同的識訓,

  • 時空復雜度的詳細分析
  • 識別并簡化遞回程序中的重復運算
  • 披上羊皮的狼
  • 適當炫技助我拿到第一份作業

演算法思路

大家都知道,一個方法自己呼叫自己就是遞回,沒錯,但這只是理解遞回的最表層的理解,

那么遞回的實質是什么?

答:遞回的實質是能夠把一個大問題分解成比它小點的問題,然后我們拿到了小問題的解,就可以用小問題的解去構造大問題的解,

那小問題的解是如何得到的?

答:用再小一號的問題的解構造出來的,小到不能再小的時候就是到了零號問題的時候,也就是 base case 了,

那么總結一下遞回的三個步驟:

Base case:就是遞回的零號問題,也是遞回的終點,走到最小的那個問題,能夠直接給出結果,不必再往下走了,否則,就會成死回圈;

拆解:每一層的問題都要比上一層的小,不斷縮小問題的 size,才能從大到小到 base case;

組合:得到了小問題的解,還要知道如何才能構造出大問題的解,

所以每道遞回題,我們按照這三個步驟來分析,把這三個問題搞清楚,代碼就很容易寫了,

斐波那契數列

這題雖是老生常談了,但相信我這里分享的一定會讓你有其他識訓,

題目描述

斐波那契數列是一位意大利的數學家,他閑著沒事去研究兔子繁殖的程序,研究著就發現,可以寫成這么一個序列:1,1,2,3,5,8,13,21… 也就是每個數等于它前兩個數之和,那么給你第 n 個數,問 F(n) 是多少,

決議

用數學公式表示很簡單:

$$f(n) = f(n-1) + f(n-2)$$

代碼也很簡單,用我們剛總結的三步:

  • base case: f(0) = 0, f(1) = 1.
  • 分解:f(n-1), f(n-2)
  • 組合:f(n) = f(n-1) + f(n-2)

那么寫出來就是:

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        } else if (N == 1) {
            return 1;
        }
        return fib(N-1) + fib(N-2);
    }
}

但是這種解法 Leetcode 給出的速度經驗只比 15% 的答案快,因為,它的時間復雜度實在是太高了!

程序分析

這就是我想分享的第一點,如何去分析遞回的程序,

首先我們把這顆 Recursion Tree 畫出來,比如我們把 F(5) 的遞回樹畫出來:

那實際的執行路線是怎樣的?

首先是沿著最左邊這條線一路到底:F(5) → F(4) → F(3) → F(2) → F(1),好了終于有個 base case 可以回傳 F(1) = 1 了,然后回傳到 F(2) 這一層,再往下走,就是 F(0),又觸底反彈,回到 F(2),得到 F(2) = 1+0 =1 的結果,把這個結果回傳給 F(3),然后再到 F(1),拿到結果后再回傳 F(3) 得到 F(3) = 左 + 右 = 2,再把這個結果返上去...

這種方式本質上是由我們計算機的馮諾伊曼體系造就的,目前一個 CPU 一個核在某一時間只能執行一條指令,所以不能 F(3) 和 F(4) 一起進行了,一定是先執行了 F(4) (本代碼把 fib(N-1) 放在前面),再去執行 F(3).

我們在 IDE 里 debug 就可以看到堆疊里面的情況:這里確實是先走的最左邊這條線路,一共有 5 層,然后再一層層往上回傳,

沒看懂的小伙伴可以看視頻講解哦~

完整版視頻還請大家移步 B 站,搜索「田小齊」「遞回」,即可看到我的視頻講解,

時間復雜度分析

如何評價一個演算法的好壞?

很多問題都有多種解法,畢竟條條大路通羅馬,但如何評價每種方法的優劣,我們一般是用大 O 運算式來衡量時間和空間復雜度,

時間復雜度:隨著自變數的增長,所需時間的增長情況,

這里大 O 表示的是一個演算法在 worst case 的表現情況,這就是我們最關心的,不然春運搶車票的時候系統 hold 不住了,你跟我說這個演算法很優秀?

當然還有其他衡量時間和空間的方式,比如

Theta: 描述的是 tight bound
Omega(n): 這個描述的是 best case,最好的情況,沒啥意義

這也給我們了些許啟發,不要說你平時表現有多好,沒有意義;面試衡量的是你在 worst case 的水平;不要說面試沒有發揮出你的真實水平,扎心的是那就是我們的真實水平,

那對于這個題來說,時間復雜度是多少呢?

答:因為我們每個節點都走了一遍,所以是把所有節點的時間加起來就是總的時間,

在這里,我們在每個節點上做的事情就是相加求和,是 O(1) 的操作,且每個節點的時間都是一樣的,所以:

總時間 = 節點個數 * 每個節點的時間

那就變成了求節點個數的數學題:

在 N = 5 時,

最上面一層有1個節點,
第二層 2 個,
第三層 4 個,
第四層 8 個,
第五層 16 個,如果填滿的話,想象成一顆很大的樹:)

這里就不要在意這個沒填滿的地方了,肯定是會有差這么幾個 node,但是大 O 表達的時間復雜度我們剛說過了,求的是 worst case.

那么總的節點數就是:
1 + 2 + 4 + 8 + 16

這就是一個等比數列求和了,當然你可以用數學公式來算,但還有個小技巧可以幫助你快速計算:

其實前面每一層的節點相加起來的個數都不會超過最后一層的節點的個數,總的節點數最多也就是最后一層節點數 * 2,然后在大 O 的時間復雜度里面常數項也是無所謂的,所以這個總的時間復雜度就是:

最后一層節點的個數:2^n

空間復雜度分析

一般書上寫的空間復雜度是指:

演算法運行期間所需占用的所有記憶體空間

但是在公司里大家常用的,也是面試時問的指的是
Auxiliary space complexity

運行演算法時所需占用的額外空間,

舉例說明區別:比如結果讓你輸出一個長度為 n 的陣列,那么這 O(n) 的空間是不算在演算法的空間復雜度里的,因為這個空間是跑不掉的,不是取決于你的演算法的,

那空間復雜度怎么分析呢?

我們剛剛說到了馮諾伊曼體系,從圖中也很容易看出來,是最左邊這條路線占用 stack 的空間最多,一直不斷的壓堆疊,也就是從 5 到 4 到 3 到 2 一直壓到 1,才到 base case 回傳,每個節點占用的空間復雜度是 O(1),所以加起來總的空間復雜度就是 O(n).

我在上面??的視頻里也提到了,不懂的同學往上翻看視頻哦~

優化演算法

那我們就想了,為什么這么一個簡簡單單的運算竟然要指數級的時間復雜度?到底是為什么讓時間如此之大,

那也不難看出來,在這棵 Recursion Tree 里,有太多的重復計算了,

比如一個 F(2) 在這里都被計算了 3 次,F(3) 被計算了 2 次,每次還都要再重新算,這不就是狗熊掰棒子嗎,真的是一把辛酸淚,

那找到了原因之后,為了解決這種重復計算,計算機采用的方法其實和我們人類是一樣的:記筆記

對很多職業來說,比如醫生、律師、以及我們工程師,為什么越老經驗值錢?因為我們見得多積累的多,下次再遇到類似的問題時,能夠很快的給出解決方案,哪怕一時解決不了,也避免了一些盲目的試錯,我們會站在過去的高度不斷進步,而不是每次都從零開始,

回到優化演算法上來,那計算機如何記筆記呢?

我們要想求 F(n),無非也就是要
記錄 F(0) ~ F(n-1) 的值
那選取一個合適的資料結構來存盤就好了,

那這里很明顯了,用一個陣列來存:

Index 0 1 2 3 4 5
F(n) 0 1 1 2 3 5

那有了這個 cheat sheet,我們就可以從前到后得到結果了,這樣每一個點就只算了一遍,用一個 for loop 就可以寫出來,代碼也非常簡單,

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        }
        if (N== 1) {
            return 1;
        }
        int[] notes = new int[N+1];
        notes[0] = 0;
        notes[1] = 1;
        for(int i = 2; i <= N; i++) {
            notes[i] = notes[i-1] + notes[i-2];
        }
        return notes[N];
    }
}

這個速度就是 100% 了~

但是我們可以看到,空間應該還有優化的余地,

那仔細想想,其實我們記筆記的時候需要記錄這么多嗎?需要從幼兒園到小學到初中到高中的筆記都留著嗎?

那其實每項的計算只取決于它前面的兩項,所以只用保留這兩個就好了,

那我們可以用一個長度為 2 的陣列來計算,或者就用 2 個變數,

更新代碼:

class Solution {
    public int fib(int N) {
        int a = 0;
        int b = 1;
        if(N == 0) {
            return a;
        }
        if(N == 1) {
            return b;
        }
        for(int i = 2; i <= N; i++) {
            int tmp = a + b;
            a = b;
            b = tmp;
        }
        return b;
    }
}

這樣我們就把空間復雜度優化到了 O(1),時間復雜度和用陣列記錄一樣都是 O(n).

這種方法其實就是動態規劃 Dynamic Programming,寫出來的代碼非常簡單,

那我們比較一下 Recursion 和 DP:

Recursion 是從大到小,層層分解,直到 base case 分解不了了再組合回傳上去;
DP 是從小到大,記好筆記,不斷進步,

也就是 Recursion + Cache = DP

如何記錄這個筆記,如何高效的記筆記,這是 DP 的難點,

有人說 DP 是拿空間換時間,但我不這么認為,這道題就是一個很好的例證,

在用遞回解題時,我們可以看到,空間是 O(n) 在堆疊上的,但是用 DP 我們可以把空間優化到 O(1),DP 可以做到時間空間的雙重優化,

其實呢,斐波那契數列在現實生活中也有很多應用,

比如在我司以及很多大公司里,每個任務要給分值,1分表示大概需要花1天時間完成,然后分值只有1,2,3,5,8這5種,(如果有大于8分的任務,就需要把它 break down 成8分以內的,以便大家在兩周內能完成,)
因為任務是永遠做不完的而每個人的時間是有限的,所以每次小組會開會,挑出最重要的任務讓大家來做,然后每個人根據自己的 available 的天數去 pick up 相應的任務,

披著羊皮的狼

那有同學可能會想,這題這么簡單,這都 2020 年了,面試還會考么?

答:真的會,

只是不能以這么直白的方式給你了,

比如很有名的爬樓梯問題:

一個 N 階的樓梯,每次能走一層或者兩層,問一共有多少種走法,

這個題這么想:

站在當前位置,只能是從前一層,或者前兩層上來的,所以 f(n) = f(n-1) + f(n-2).

這題是我當年面試時真實被問的,那時我還在寫 python,為了炫技,還用了lambda function:

f = lambda n: 1 if n in (1, 2) else f(n-1) + f(n-2)

遞回的寫法時間復雜度太高,所以又寫了一個 for loop 的版本

def fib(n)
  a, b = 1, 1
  for i in range(n-1):
	a, b = b, a+b
  return a 

然后還寫了個 caching 的方法:

def cache(f):
	memo = {}
	def helper(x):
		if x not in memo:
			memo[x] = f(x)
		return memo[x]
	return helper
@cache
def fibR(n):
	if n==1 or n==2: return 1
	return fibR(n-1) + fibR(n-2)

還順便和面試官聊了下 tail recursion:

tail recursion 尾遞回:就是遞回的這句話是整個方法的最后一句話,

那這個有什么特別之處呢?

尾遞回的特點就是我們可以很容易的把它轉成 iterative 的寫法,當然有些智能的編譯器會自動幫我們做了(不是說顯性的轉化,而是在運行時按照 iterative 的方式去運行,實際消耗的空間是 O(1))

那為什么呢?

因為回來的時候不需要 backtrack,遞回這里就是最后一步了,不需要再往上一層返值,

def fib(n, a=0, b=1):
	if n==0: return a
	  if n==1: return b
	return fib(n-1, b, a+b)

最終,拿出了我的殺手锏:lambda and reduce

fibRe = lambda n: reduce(lambda x, n: [x[1], x[0]+x[1]], range(n), [0, 1])

看到面試官滿意的表情后,就開始繼續深入的聊了...

所以說,不要以為它簡單,同一道題可以用七八種方法來解,分析好每個方法的優缺點,引申到你可以引申的地方,展示自己扎實的基本功,這場面試其實就是你 show off 的機會,這樣才能騙過面試官啊~lol

如果你喜歡這篇文章,記得給我點贊留言哦~你們的支持和認可,就是我創作的最大動力,我們下篇文章見!

我是小齊,紐約程式媛,終生學習者,每天晚上 9 點,云自習室里不見不散!

更多干貨文章見我的 Github: https://github.com/xiaoqi6666/NYCSDE

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/95802.html

標籤:其他

上一篇:leetcode560題解【前綴和+哈希】

下一篇:程式流程圖的基本畫法大全

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more