日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

详解「递归」正确的打开方式

發(fā)布時(shí)間:2025/4/5 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解「递归」正确的打开方式 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

前言

遞歸,是一個(gè)非常重要的概念,也是面試中非常喜歡考的。因?yàn)樗坏芸疾煲粋€(gè)程序員的算法功底,還能很好的考察對(duì)時(shí)間空間復(fù)雜度的理解和分析。

本文只講一題,也是幾乎所有算法書講遞歸的第一題,但力爭(zhēng)講出花來(lái),在這里分享四點(diǎn)不一樣的角度,讓你有不同的收獲。

  • 時(shí)空復(fù)雜度的詳細(xì)分析

  • 識(shí)別并簡(jiǎn)化遞歸過(guò)程中的重復(fù)運(yùn)算

  • 披上羊皮的狼

  • 適當(dāng)炫技助我拿到第一份工作

算法思路

大家都知道,一個(gè)方法自己調(diào)用自己就是遞歸,沒錯(cuò),但這只是對(duì)遞歸最表層的理解。

那么遞歸的實(shí)質(zhì)是什么?

答:遞歸的實(shí)質(zhì)是能夠把一個(gè)大問(wèn)題分解成比它小點(diǎn)的問(wèn)題,然后我們拿到了小問(wèn)題的解,就可以用小問(wèn)題的解去構(gòu)造大問(wèn)題的解。

那小問(wèn)題的解是如何得到的?

答:用再小一號(hào)的問(wèn)題的解構(gòu)造出來(lái)的,小到不能再小的時(shí)候就是到了零號(hào)問(wèn)題的時(shí)候,也就是 base case 了。

那么總結(jié)一下遞歸的三個(gè)步驟:

Base case:就是遞歸的零號(hào)問(wèn)題,也是遞歸的終點(diǎn),走到最小的那個(gè)問(wèn)題,能夠直接給出結(jié)果,不必再往下走了,否則,就會(huì)成死循環(huán);

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

組合:得到了小問(wèn)題的解,還要知道如何才能構(gòu)造出大問(wèn)題的解。

所以每道遞歸題,我們按照這三個(gè)步驟來(lái)分析,把這三個(gè)問(wèn)題搞清楚,代碼就很容易寫了。

斐波那契數(shù)列

這題雖是老生常談了,但相信我這里分享的一定會(huì)讓你有其他收獲。

題目描述

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

解析

用數(shù)學(xué)公式表示很簡(jiǎn)單:

代碼也很簡(jiǎn)單,用我們剛總結(jié)的三步:

  • base case: f(0) = 0, f(1) = 1.

  • 分解:f(n-1), f(n-2)

  • 組合:f(n) = f(n-1) + f(n-2)

那么寫出來(lái)就是:

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 給出的速度經(jīng)驗(yàn)只比 15% 的答案快,因?yàn)?#xff0c;它的時(shí)間復(fù)雜度實(shí)在是太高了!

過(guò)程分析

那這就是我想分享的第一點(diǎn),如何去分析遞歸的過(guò)程。

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

那實(shí)際的執(zhí)行路線是怎樣的?

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

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

我們?cè)?IDE 里 debug 就可以看到棧里面的情況:這里確實(shí)是先走的最左邊這條線路,一共有 5 層,然后再一層層往上返回。

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

?

時(shí)間復(fù)雜度分析

如何評(píng)價(jià)一個(gè)算法的好壞?

很多問(wèn)題都有多種解法,畢竟條條大路通羅馬。但如何評(píng)價(jià)每種方法的優(yōu)劣,我們一般是用大 O 表達(dá)式來(lái)衡量時(shí)間和空間復(fù)雜度。

時(shí)間復(fù)雜度:隨著自變量的增長(zhǎng),算法所需時(shí)間的增長(zhǎng)情況。

這里大 O 表示的是一個(gè)算法在?worst case?的表現(xiàn)情況,這就是我們最關(guān)心的,不然春運(yùn)搶車票的時(shí)候系統(tǒng) hold 不住了,你跟我說(shuō)這個(gè)算法很優(yōu)秀?

當(dāng)然還有其他衡量時(shí)間和空間的方式,比如

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

這也給我們了些許啟發(fā),不要說(shuō)你平時(shí)表現(xiàn)有多好,沒有意義;面試衡量的是你在 worst case 的水平;不要說(shuō)面試沒有發(fā)揮出你的真實(shí)水平,扎心的是那就是我們的真實(shí)水平。

那對(duì)于這個(gè)題來(lái)說(shuō),時(shí)間復(fù)雜度是多少呢?

答:因?yàn)槲覀兠總€(gè)節(jié)點(diǎn)都走了一遍,所以是把所有節(jié)點(diǎn)的時(shí)間加起來(lái)就是總的時(shí)間。

在這里,我們?cè)诿總€(gè)節(jié)點(diǎn)上做的事情就是相加求和,是 O(1) 的操作,且每個(gè)節(jié)點(diǎn)的時(shí)間都是一樣的,所以:

總時(shí)間 = 節(jié)點(diǎn)個(gè)數(shù) * 每個(gè)節(jié)點(diǎn)的時(shí)間

那就變成了求節(jié)點(diǎn)個(gè)數(shù)的數(shù)學(xué)題:

在 N = 5 時(shí),

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

這里就不要在意這個(gè)沒填滿的地方了,肯定是會(huì)有差這么幾個(gè) node,但是大 O 表達(dá)的時(shí)間復(fù)雜度我們剛說(shuō)過(guò)了,求的是 worst case.

那么總的節(jié)點(diǎn)數(shù)就是:
1 + 2 + 4 + 8 + 16

這就是一個(gè)等比數(shù)列求和了,當(dāng)然你可以用數(shù)學(xué)公式來(lái)算,但還有個(gè)小技巧可以幫助你快速計(jì)算:

其實(shí)前面每一層的節(jié)點(diǎn)相加起來(lái)的個(gè)數(shù)都不會(huì)超過(guò)最后一層的節(jié)點(diǎn)的個(gè)數(shù),總的節(jié)點(diǎn)數(shù)最多也就是最后一層節(jié)點(diǎn)數(shù) * 2,然后在大 O 的時(shí)間復(fù)雜度里面常數(shù)項(xiàng)也是無(wú)所謂的,所以這個(gè)總的時(shí)間復(fù)雜度就是:

最后一層節(jié)點(diǎn)的個(gè)數(shù):2^n

沒看懂?別慌,去 B 站/油管看我的視頻講解哦,搜「田小齊」就好了。

空間復(fù)雜度分析

一般書上寫的空間復(fù)雜度是指:

算法運(yùn)行期間所需占用的所有內(nèi)存空間

但是在公司里大家常用的,也是面試時(shí)問(wèn)的指的是
Auxiliary space complexity:

運(yùn)行算法時(shí)所需占用的額外空間。

舉例說(shuō)明區(qū)別:比如結(jié)果讓你輸出一個(gè)長(zhǎng)度為 n 的數(shù)組,那么這 O(n) 的空間是不算在算法的空間復(fù)雜度里的,因?yàn)檫@個(gè)空間是跑不掉的,不是取決于你的算法的。

那空間復(fù)雜度怎么分析呢?

我們剛剛說(shuō)到了馮諾伊曼體系,從圖中也很容易看出來(lái),是最左邊這條路線占用 stack 的空間最多,一直不斷的壓棧,也就是從 5 到 4 到 3 到 2 一直壓到 1,才到 base case 返回,每個(gè)節(jié)點(diǎn)占用的空間復(fù)雜度是 O(1),所以加起來(lái)總的空間復(fù)雜度就是?O(n).

我在上面👆的視頻里也提到了,不懂的同學(xué)往上翻看視頻哦~

優(yōu)化算法

那我們就想了,為什么這么一個(gè)簡(jiǎn)簡(jiǎn)單單的運(yùn)算竟然要指數(shù)級(jí)的時(shí)間復(fù)雜度?到底是為什么讓時(shí)間如此之大。

那也不難看出來(lái),在這棵?Recursion Tree?里,有太多的重復(fù)計(jì)算了。

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

那找到了原因之后,為了解決這種重復(fù)計(jì)算,計(jì)算機(jī)采用的方法其實(shí)和我們?nèi)祟愂且粯拥?#xff1a;記筆記。

對(duì)很多職業(yè)來(lái)說(shuō),比如醫(yī)生、律師、以及我們工程師,為什么越老經(jīng)驗(yàn)值錢?因?yàn)槲覀円姷枚喾e累的多,下次再遇到類似的問(wèn)題時(shí),能夠很快的給出解決方案,哪怕一時(shí)解決不了,也避免了一些盲目的試錯(cuò),我們會(huì)站在過(guò)去的高度不斷進(jìn)步,而不是每次都從零開始。

回到優(yōu)化算法上來(lái),那計(jì)算機(jī)如何記筆記呢?

我們要想求 F(n),無(wú)非也就是要
記錄 F(0) ~ F(n-1) 的值,
那選取一個(gè)合適的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)就好了。

那這里很明顯了,可以用之前講過(guò)的?HashMap?(沒看過(guò)的點(diǎn)進(jìn)去看哦)或者用一個(gè)數(shù)組來(lái)存:

Index012345
F(n)011235

那有了這個(gè) cheat sheet,我們就可以從前到后得到結(jié)果了,這樣每一個(gè)點(diǎn)就只算了一遍,用一個(gè) for loop 就可以寫出來(lái),代碼也非常簡(jiǎn)單。

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];} }

這個(gè)速度就是 100% 了~

但是我們可以看到,空間應(yīng)該還有優(yōu)化的余地。

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

那其實(shí)每項(xiàng)的計(jì)算只取決于它前面的兩項(xiàng),所以只用保留這兩個(gè)就好了。

那我們可以用一個(gè)長(zhǎng)度為 2 的數(shù)組來(lái)計(jì)算,或者就用 2 個(gè)變量。

更新代碼:

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;} }

這樣我們就把空間復(fù)雜度優(yōu)化到了?O(1),時(shí)間復(fù)雜度和用數(shù)組記錄一樣都是?O(n).

這種方法其實(shí)就是動(dòng)態(tài)規(guī)劃?Dynamic Programming,寫出來(lái)的代碼非常簡(jiǎn)單。

那我們比較一下 Recursion 和 DP:

Recursion?是從大到小,層層分解,直到 base case 分解不了了再組合返回上去;
DP?是從小到大,記好筆記,不斷進(jìn)步。

也就是?Recursion + Cache = DP

如何記錄這個(gè)筆記,如何高效的記筆記,這是 DP 的難點(diǎn)。

有人說(shuō) DP 是拿空間換時(shí)間,但我不這么認(rèn)為,這道題就是一個(gè)很好的例證。

在用遞歸解題時(shí),我們可以看到,空間是 O(n) 在棧上的,但是用 DP 我們可以把空間優(yōu)化到 O(1),DP 可以做到時(shí)間空間的雙重優(yōu)化。

其實(shí)呢,斐波那契數(shù)列在現(xiàn)實(shí)生活中也有很多應(yīng)用。

比如在我司以及很多大公司里,每個(gè)任務(wù)要給分值,1分表示大概需要花1天時(shí)間完成,然后分值只有1,2,3,5,8這5種,(如果有大于8分的任務(wù),就需要把它 break down 成8分以內(nèi)的,以便大家在兩周內(nèi)能完成。)
因?yàn)槿蝿?wù)是永遠(yuǎn)做不完的而每個(gè)人的時(shí)間是有限的,所以每次小組會(huì)開會(huì),挑出最重要的任務(wù)讓大家來(lái)做,然后每個(gè)人根據(jù)自己的 available 的天數(shù)去 pick up 相應(yīng)的任務(wù)。

披著羊皮的狼

那有同學(xué)可能會(huì)想,這題這么簡(jiǎn)單,這都 2020 年了,面試還會(huì)考么?

答:真的會(huì)。

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

比如很有名的爬樓梯問(wèn)題:

一個(gè) N 階的樓梯,每次能走一層或者兩層,問(wèn)一共有多少種走法。

這個(gè)題這么想:

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

這題是我當(dāng)年面試時(shí)真實(shí)被問(wèn)的,那時(shí)我還在寫 python,為了炫技,還用了lambda function:

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

遞歸的寫法時(shí)間復(fù)雜度太高,所以又寫了一個(gè) for loop 的版本

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

然后還寫了個(gè) 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?1return?fibR(n-1)?+?fibR(n-2)

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

tail recursion 尾遞歸:就是遞歸的這句話是整個(gè)方法的最后一句話。

那這個(gè)有什么特別之處呢?

尾遞歸的特點(diǎn)就是我們可以很容易的把它轉(zhuǎn)成 iterative 的寫法,當(dāng)然有些智能的編譯器會(huì)自動(dòng)幫我們做了(不是說(shuō)顯性的轉(zhuǎn)化,而是在運(yùn)行時(shí)按照 iterative 的方式去運(yùn)行,實(shí)際消耗的空間是 O(1))

那為什么呢?

因?yàn)榛貋?lái)的時(shí)候不需要 backtrack,遞歸這里就是最后一步了,不需要再往上一層返值。

def?fib(n,?a=0,?b=1):if?n==0:?return?aif?n==1:?return?breturn?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])

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

所以說(shuō),不要以為它簡(jiǎn)單,同一道題可以用七八種方法來(lái)解,分析好每個(gè)方法的優(yōu)缺點(diǎn),引申到你可以引申的地方,展示自己扎實(shí)的基本功,這場(chǎng)面試其實(shí)就是你 show off 的機(jī)會(huì),這樣才能騙過(guò)面試官啊~lol

C語(yǔ)言代碼如下:

#include <stdio.h>int Fibonacci(int n) {if(n == 0){return 0;}if(n == 1){return 1;}return Fibonacci(n - 1) + Fibonacci(n - 2); }int Fibonacci_DP(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; }int main() {#define N 20int data = 0;/* 我的第一個(gè) C 程序 */printf("Hello, World! \n");data = Fibonacci(N);printf("data = %d; \n",data);data = Fibonacci_DP(N);printf("data = %d; \n",data);return 0; }

?

這就是本文的所有內(nèi)容了,不知道大家看完感受如何?留言告訴我你的感受吧~

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的详解「递归」正确的打开方式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲欧美亚洲 | 国产综合精品视频 | 国产精品一级无码 | 久久婷婷色综合 | 亚洲激情三区 | 三级理论电影 | 国产精品国产精品国产专区 | 亚洲一区二区三区激情 | 日本美女一区二区三区 | 91高清网站 | 性欧美在线视频观看 | 人人干干 | 国产真人无码作爱视频免费 | 激情综合文学 | 亚洲精品亚洲人成人网 | 内射无码专区久久亚洲 | 国产操操操 | 久久成人视屏 | 国产成人综合av | 国产一级久久久 | 无码免费一区二区三区免费播放 | 亚洲品质自拍视频 | 老头av | 久久亚洲aⅴ无码精品 | 欧美美女喷水 | 中文字幕国产亚洲 | 成人午夜av在线 | 无码人妻aⅴ一区二区三区 国产高清一区二区三区四区 | 免费视频91蜜桃 | 日韩视频三区 | 口舌奴vk | 99久久久国产精品 | 国语对白精彩对话 | 婷婷激情久久 | 俄罗斯女人裸体性做爰 | 黄色av网站免费观看 | 91av亚洲 | 毛片网络 | 国产高潮国产高潮久久久91 | 日韩中文字幕免费在线观看 | 真人真事免费毛片 | 波多野一区二区 | 老司机午夜剧场 | 久久噜噜噜精品国产亚洲综合 | 手机看片日韩在线 | 久久国产乱子 | 91喷水视频| 黄色小毛片 | 黄页av| 中文字幕精品久久久久人妻红杏ⅰ | 精品一区二区免费看 | 色视频国产 | 欧美一区二区三区大屁股撅起来 | 国产日韩欧美精品在线 | www天天干| 成人黄色在线播放 | 国产精品久久在线 | 国产91精品久久久 | 女人的天堂av | av影视网| 少妇人妻真实偷人精品视频 | 国产精品国产三级国产专区52 | 日本高清有码 | 欧美成人aaa | 日韩特级黄色片 | 国产精品无码久久久久高潮 | 欧美精品自拍视频 | 黄色片网站免费在线观看 | 亚洲免费影院 | 日韩午夜剧场 | 成人免费看片网站 | 午夜国产福利在线观看 | 国产精品精品国产色婷婷 | 噜噜色av | 日本韩国在线观看 | 国产精品九九九九九 | 国产视频一区二区三区在线播放 | 波多野结衣视频免费 | 久久免费视频网站 | 亚洲三级a| zzji欧美大片| 日本黄色一级网站 | 亚洲日皮 | 亚洲九九 | 黑料视频在线观看 | 久热久色| 亚洲成人一区二区在线观看 | 亚洲色婷婷一区二区三区 | 日本福利一区二区 | 91国产视频在线观看 | 嫩草视频一区二区三区 | 日韩黄色网| 毛片在线免费观看网站 | 欧美高清视频一区二区 | 欧美一区二区在线观看 | 国产吃瓜在线 | 骚视频在线观看 | 成年人看的网站 | 国产精品久久久久久久久久 |