聊一聊前端算法面試——遞歸

文章首發於:github.com/USTB-musion…前端

寫在前面

今天來聊一聊前端面試中出現頻率很是高的一種算法思想——「遞歸」。git

先看下幾個常見的面試題:github

  1. 假如樓梯有n個臺階,每次能夠走1個或2個臺階,請問走完這n個臺階有幾種走法❓
  2. 如何用遞歸思想實現深拷貝❓
  3. 如何用遞歸思想實現數組的扁平化❓

你能夠先思考一下如何回答上邊的問題🤔,而後帶着答案來閱覽接下來的內容。面試

如何編寫遞歸代碼❓

遞歸思想在前端面試中很是常見,除了上面的一些題目以外,二叉樹的前中後序遍歷,斐波那契數列等都用到了遞歸的思想。簡單地理解遞歸就是:本身調用本身。那如何編寫遞歸的代碼呢❓ 在筆者看來:算法

主要有兩個關鍵步驟:數組

  • 寫出遞歸公式
  • 找到終止條件

先來看個簡單的例子:如何求1+2+3+4+...+n的和?相信用for循環的方法你們都知道如何編寫:bash

function sum(n) {
    var total = 0
    for (int i = 1; i <= n; i++) {
    	total = total + i
    }
    return total
}
複製代碼

那如何改成遞歸的寫法呢?函數

第一步: 寫出遞歸公式性能

細心觀察就會發現,其實就是n與n-1和n-2的關係優化

sum(n) = sum(n-1) + n
···
···
···
sum(100) = sum(99) + 100
sum(99) = sum(98) + 99
···
···
···
sum(5) = sum(4) + 5
sum(4) = sum(3) + 4
sum(3) = sum(2) + 3
sum(2) = sum(1) + 2
sum(1) = 1
複製代碼

將如上轉化成遞歸公式就是

function sum(n) {
    return sum(n-1) + n
}
複製代碼

第二步:找出終止條件

遞歸公式寫出來了,那麼遞歸代碼就完成了一大半。如今來看一下上述問題的終止條件是什麼呢,即sum(1)= 1;

結合遞歸公式和終止條件,1+2+3+4+...+n求和的遞歸代碼以下:

function sum(n) {
    if( n ===1 ) return 1
    return sum(n-1) + n
}
複製代碼

面試題1: 樓梯問題

假如樓梯有n個臺階,每次能夠走1個或2個臺階,請問走完這n個臺階有幾種走法❓

按照咱們上面的思路,先寫出遞歸公式。那這道題咱們如何去找出遞歸公式呢。假設有3個臺階,咱們能夠有3種走法:

1 1 1
1 2
2 1
複製代碼

第一種是每次都是走1個臺階。第二種是第一步走1個臺階,第二步走2個臺階。第三種是第一步走2個臺階,第二步走1個臺階。

寫出遞歸公式:

那若是有n個臺階呢?咱們認真思考下就會發現,第1步的走法有兩類:第一種是第一步走1個臺階,第二種是第二步走2個臺階。因此n個臺階的走法就能夠分爲:走完1個臺階後的n-1種走法,加上走完2個臺階後的n-2種走法,用遞歸公式表示就是:

function climbStairs(n) {
    return climbStairs(n - 1) + climbStairs(n - 2)
}
複製代碼

找到終止條件:

climbStairs(n) = climbStairs(n-1) + climbStairs(n-2)
climbStairs(n-1) = climbStairs(n-2) + climbStairs(n-3)
···
···
···
climbStairs(5) = climbStairs(4) + climbStairs(3)
climbStairs(4) = climbStairs(3) + climbStairs(2)
climbStairs(3) = climbStairs(2) + climbStairs(1)
climbStairs(2) = 2
climbStairs(1) = 1
複製代碼

從上面的推導能夠看出:終止條件爲:

climbStairs(2) = 2
climbStairs(1) = 1
複製代碼

綜上所述,解決爬樓梯的代碼以下:

function climbStairs(n) {
  if (n == 1) return 1
  if (n == 2) return 2
  return climbStairs(n-1) + climbStairs(n-2)
}
複製代碼

固然,能夠對上述題目作一個memorize操做,性能會好不少:

var calculated = []

function climbStairs(n) {

    if(n == 1) {
        return 1
    }else if (n == 2) {
        return 2
    }else {
        if(!calculated[n-1]){
            calculated[n-1] = climbStairs(n-1)
        }

        if(!calculated[n-2]){
            calculated[n-2] = climbStairs(n-2)
        }
        return calculated[n-1] + calculated[n-2]
    }

}
複製代碼

解決完爬樓梯問題以後,思考下斐波那契數列問題的求解,有木有發現是同樣的問題和思路:)

面試題2:實現深拷貝

如何用遞歸思想實現深拷貝❓若是要實現深拷貝那麼就須要考慮將對象的屬性, 與屬性的屬性,都拷貝過來, 這種狀況下就很是適合使用遞歸思想來解決,在拷貝的適合判斷屬性值的類型,若是是對象則遞歸調用deeplClone函數,不然直接返回該屬性值:

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    // // 根據obj的類型判斷是新建一個數組仍是對象
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}
複製代碼

面試題3:如何把數組拍平

如何用遞歸思想實現數組的扁平化❓即如何把[1, [2], [3, [4, [5]]]]拍平獲得[1,2,3,4,5]❓

const flatten = (arr) => {
  let result = [];
  arr.forEach((item, i, arr) => {
    // 若爲數組,遞歸調用 faltten,並將結果與result合併
    if (Array.isArray(item)) {
      result = result.concat(flatten(item));
    } else {
      result.push(arr[i])
    }
  })
  return result;
};
const arr = [1, [2, [3, 4, 5]]];
console.log(flatten(arr)); // [1, 2, 3, 4, 5]

複製代碼

總結

編寫遞歸代碼的關鍵在於找出遞歸公式和終止條件,最後將它們翻譯成代碼。遞歸代碼雖然比較簡潔。可是也有不少弊端,如性能不是很高效,這時候要作memorize等一些操做來優化性能。容易產生堆棧溢出等問題,因此咱們在寫代碼的時候要注意這些。

相關文章
相關標籤/搜索