面試官在「逗」你系列 到底應該怎麼爬樓梯?!

2021-10-19 08:33:30 字數 4902 閱讀 4529

演算法題是在面試過程中考察候選人邏輯思維能力、手寫**能力的一種方式,因為有一句古話說的好:「說一千道一萬,不如寫段**看一看」。

今天我們就來個單刀直入,直奔主題,從乙個真實面試題到底怎麼爬樓梯來聊一聊演算法中的動態規劃

小明家有一樓梯共有10級台階,每次可以爬1級或2級,問小明爬到第10級台階,一共有多少種走法?

為什麼是「小明」呢?這是個奇怪的問題~

很多同學在第一次遇到這個爬樓梯的問題可能會比較,不知道該如何來解決。我們首先需要做的就是尋找這個問題的關鍵點:每次只能爬1級或2級

小明每次只能爬1級或2級,那麼對於爬到第10級台階來說,最後一次操作為走1級(此時處於第9級台階上)或走2級(此時處於第8級台階上)。 假定我們有個表示式f可以來計算到達某階台階的走法,那麼對於第10階來說,這個表示式就應該為:f(10) = f(9) + f(8)

對於這個表示式,是不是有種瞬間回到那初、高中的年代~

按如上規則,再次考慮,爬到第9級台階時,最後一次操作為走1級(此時處於第8級台階上)或走2級(此時處於第7級台階上),此處的表示式為:f(9) = f(8) + f(7)

依次處理,當爬到第3級台階時,計算的表示式就是f(3) = f(2) + f(1)

那爬到第2級台階有幾種方式呢:每次走1級或者一次走2級,也就是一共有2種走法,f(2) = 2

爬到第1級台階的方式肯定只有一種:走1級,f(1) = 1

/**

* @method climbstairs

* @description 爬樓梯

* @param n 樓梯台階數

* @return 一共有多少種走法

*/function climbstairs (n) ;

if (n === 2) ;

let num = 0;

num = climbstairs(n - 1) + climbstairs(n - 2);

return num;

}// 呼叫函式,輸出結果

let num = climbstairs(10);

console.log(num); // 89

congratulations~我們已經完成啦,得到了正確的結果。

就在你滿臉微笑、志得意滿的向面試官講解思路的時候,面試官十有**會一副老狐狸得逞的樣子,繼續問你,假如n的值比較大的,如40之類的,上面定義的climbstairs的執行效能如何。

我們來看下執行的效能:

測試**如下:

console.time();

let num = climbstairs(40);

console.log(num);

console.timeend()

我的mac配置如下:

macbook pro 

處理器:2.5 ghz 四核intel core i7

記憶體: 16gb

連續執行三次資料如下:

序號結果

執行時間

1165580141

811.077ms

2165580141

817.025ms

3165580141

814.803ms

注:在執行過程中有卡頓,不是瞬間輸出,如果執行的是climbstairs(100),你應該會瞬間聽到風扇啟動的嗚嗚聲

在上面**climbstairs的基礎上我們來進行優化處理。我們仔細分析**的執行流程,就會發現有很多重複計算的地方,比如說f(5)就會在f(6-1)f(7-2)時被重複計算,這就浪費了時間和效能。

那我們就選擇使用空間換時間的策略,設定物件numbers,儲存爬到某級台階的結果,避免重複計算,numbers物件的結果如下:

let numbers =
優化後**如下:

/**

* @method climbstairs

* @description 爬樓梯

* @param n 樓梯台階數

* @return 一共有多少種走法

*/function climbstairs (n) ;

let tmpclimbstairs = function (n)

// 不存在的資料,進行計算

let num = tmpclimbstairs(n - 1) + tmpclimbstairs(n - 2);

// 計算完成後,存放如numbers中,下次可以直接使用

numbers[n] = num;

// 返回結果

return num;

} // 計算結果

let num = tmpclimbstairs(n);

// 返回結果

return num;

}

相同環境下,我們再來執行測試,連續執行三次資料如下:

序號結果

執行時間

1165580141

7.100ms

2165580141

7.478ms

3165580141

6.260ms

消耗的時間竟然相差百倍之多,it's amazing!說明我們使用空間換時間的策略是正確的。

執行結果幾乎是瞬間輸出的,執行如**奶茶般順滑~此時此刻你可以再次執行climbstairs(100)來體驗下絕對的效能飆公升!

這道面試題處理成這樣已經是非常ok的了,但是如果你已經感到徹底滿足,為自己的聰明才智感到驕傲了,你就會聽到面試官可愛(恨)的聲音傳來:」還有別的方法或效能更好的方法來實現嗎?「

是不是心中一口老血想噴出來面試官是不是故意的,是不是在針對我

哈哈,不慌不慌,小場面~

遞迴與遞推是兩種不同的看待、分析問題的思路。

遞迴:自頂向下的處理邏輯,有相應的臨界點(終止遞迴的點);

遞推:自底向上的處理邏輯,到達目標點結束。

我們重新使用遞推的方式再來看這個問題。

爬到第1級台階,有1種方式。f(1) = 1

爬到第2級台階,有2種方式:每次1級或1次2級。f(2) = 2

爬到第3級台階的情況呢?

不要忘了我們之前分析的關鍵點:每次只能1級或2級, 對於第3級台階來說,可以是從第1級台階出發也可以是從第2級台階出發, 所以f(3) = f(2) + f(1)

同理可得爬到第4級台階的情況,f(4) = f(3) + f(2)

我們得出乙個結論:對於第n(n > 2)級台階,其表示式為f(n) = f(n-1) + f(n-2)。那麼我們在結算的過程中,每次都記錄下f(n-1)f(n-2)的值,逐級遷移這個值,就可以得到f(n)了。

現在climbstairs**如下:

/**

* @method climbstairs

* @description 爬樓梯

* @param n 樓梯台階數

* @return 一共有多少種走法

*/function climbstair (n) else

// 返回結果

return num;

}}

這一次我們直接在時間複雜度上降低了,變成了o(n),執行起來更加是和那啥一樣,流暢、順滑~

我們來看下測試效果,連續執行三次測試結果如下: |序號|結果|執行時間| |----|----|----| |1| 165580141 | 6.570ms | |2| 165580141 | 6.647ms | |3| 165580141 | 6.658ms |

相對於遞迴的實現方式,遞推的實現從時間複雜度上更低,執行也會更高效~

此時此刻,這個爬樓梯的問題終於是回答圓滿了,這個時候面試官看你就會像丈母娘看女婿一樣,怎麼看怎麼可愛

動態規劃的演算法問題有很多種不同的形式,爬樓梯是其中的一種。在這裡胡哥要給大家留一道面試題啦,看看大家對動態規劃是不是有了深刻的理解。

面試真題如下:

你是乙個有信仰的強盜,有一排房屋等待你去搶劫,在搶劫中不能相鄰的房屋不能搶,只能間隔乙個或多個房屋進行搶劫,房屋中錢財都是非負整數,資料格式如下:[3, 4, 5, 2, 1, 1],請計算出你能搶到的最大金額是多少。

這個強盜相當有信仰,竟然不都搶走~

胡哥有話說,專注於大前端技術領域,分享前端系統架構,框架實現原理,最新最高效的技術實踐!

面試官在「逗」你系列 陣列去重你會幾種呀?

陣列去重是乙個老生常談的話題,也是前端童鞋在面試時的一道高頻題。本文將深入的探索陣列去重的原理及實現,為各位小夥伴提供多種可以反手 調戲 面試官的解決方案。話不多說,上去就來一梭子.一般我們都會建立臨時變數tmp,儲存不重複的元素 以陣列元素儲存或物件的鍵來儲存 遍歷待去重陣列arr,依次判斷tmp...

面試官在「逗」你系列 陣列去重你會幾種呀?

陣列去重是乙個老生常談的話題,也是前端童鞋在面試時的一道高頻題。本文將深入的探索陣列去重的原理及實現,為各位小夥伴提供多種可以反手 調戲 面試官的解決方案。話不多說,上去就來一梭子.一般我們都會建立臨時變數tmp,儲存不重複的元素 以陣列元素儲存或物件的鍵來儲存 遍歷待去重陣列arr,依次判斷tmp...

面試官在「逗」你系列 陣列去重你會幾種呀?

陣列去重是乙個老生常談的話題,也是前端童鞋在面試時的一道高頻題。本文將深入的探索陣列去重的原理及實現,為各位小夥伴提供多種可以反手 調戲 面試官的解決方案。話不多說,上去就來一梭子.一般我們都會建立臨時變數tmp,儲存不重複的元素 以陣列元素儲存或物件的鍵來儲存 遍歷待去重陣列arr,依次判斷tmp...