面試題之Javascript實現1萬的階乘

前言

最近面試的時候遇到一道面試題,就是實現10000!,當時的第一反應是直接用遞歸實現:git

function factorial_recursion(n){
    if(n <= 1) return 1;

    return n * factorial_recursion(n-1)
}
複製代碼

可是這樣就會存在問題,Js中最大的安全整數爲2^53- 1,10000!結果溢出該範圍,代碼運行結果爲Infinity,沒法計算出正確的結果。github

那麼如何才能計算大數據的階乘呢?面試

BigInt

可使用Js最新的基本數據類型BigInt,BigInt數據類型支持範圍更大的整數值,能夠解決整數溢出問題。算法

BigInt數據經過BigInt構造函數建立,修改代碼以下:數組

function factorial_recursion(n){

    if(n <= 1) return 1;

    return BigInt(n) * BigInt(factorial_recursion(n-1))
}
複製代碼

經過factorial_recursion(10000)就能夠得出結果。瀏覽器

但當計算更高數值的階乘時,好比求20000的階乘,出現棧溢出的狀況。安全

那麼如何才能解決棧溢出問題?bash

平方差實現

算法思路函數

能夠從減小乘法運算的次數角度出發,階乘運算可轉換爲若干個平方差的積,使得階乘只須要n/2次乘法,而且得出規律平方差之間的差是連續的奇數。除了平方差乘數,其餘乘數根據n爲奇數或偶數也有不一樣規律。大數據

算法分析

當計算9的階乘時:

1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 

取中間值爲基值: (5 - 4) * (5 - 3) * (5 - 2) * (5 - 1) * 5 * (5 + 1) * (5 + 2) * (5 + 3) * (5 +4)

調換位置:(5 - 1) * (5 + 1) * (5 - 2) * (5 + 2) * (5 - 3) * (5 + 3) * (5 - 4) * (5 + 4) * 5

合併爲平方差:(5^2 - 1) * (5^2 - 2^2) * (5^2 - 3^2) * (5^2 - 4^2) * 5

計算平方差結果: 24 * 21 * 16 * 9  

得出規律:2四、2一、1六、9之間的差分別爲基數三、五、7,奇數基礎乘數爲 n / 2
複製代碼

當計算10的階乘時:

1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10

取中間值位基值: (5 - 4) * (5 - 3) * (5 - 2) * (5 - 1) * 5 * (5 + 1) * (5 + 2) * (5 + 3) * (5 +4) * (5 + 5)

調換位置:(5 - 1) * (5 + 1) * (5 - 2) * (5 + 2) * (5 - 3) * (5 + 3) * (5 - 4) * (5 + 4) * 5 * (5 + 5)

合併爲平方差:(5^2 - 1) * (5^2 - 2^2) * (5^2 - 3^2) * (5^2 - 4^2) * 5 * (5 + 5)

平方差結果: 24 * 21 * 16 * 9  

得出規律:2四、2一、1六、9之間的差分別爲基數三、五、7,偶數基礎乘數爲 n / 2 * n
複製代碼

代碼實現以下:

function factorial_square(n){

    if(n <= 1) return 1;

    const middle = Math.ceil(n / 2);    //取中間值

    let tmp = middle * middle,
        result = n & 1 == 1 ? middle : middle * n;  //奇偶數的基礎乘數規律不一樣

    for(let i = 1 ; i <= n - 2 ; i += 2){   //連續減奇數得出各項乘數
        tmp -= i;
        result = BigInt(result) * BigInt(tmp);
    }

    return result;
}
複製代碼

BigInt兼容性並不友好,Chrome瀏覽器在67+版本中才支持該數據類型。

在不支持BigInt的瀏覽器中怎麼計算大數據階乘呢?

數組存儲實現

算法思路

可使用數組來存儲大數據結果的每位數,如result[0]存儲個位數,result[1]存儲十位數,以此類推。計算每位數時須要加上上一個位數得出的進位,最後再將數組反轉並拼接,就能夠得出大數據結果。

算法分析

以5! = 1 * 2 * 3 * 4 * 5爲例:

實現10萬的階乘

代碼實現

function factorial_array(n){

    let result = [1],   //存儲結果
        digit = 1,      //位數,從第1位開始
        count ,         //每次計算的結果
        num ,           //階乘的計算到的第幾個
        i ,             //result中每一項
        carry;          //每次得數的進位

    for(num = 2 ; num <= n ; num++){
        for(i = 1 , carry = 0 ; i <= digit ; i++){
            count = result[i - 1] * num + carry;        //每一項計算結果
            result[i - 1] = count % 10;                 //將一個數的每一位數利用數組進行儲存
            carry = (count - result[i - 1]) / 10        //記錄進位
        }

        while (carry) { //若是還有進位,繼續存儲
            result[digit] = carry % 10;
            carry = (carry - result[digit]) / 10;
            digit++;
        }
    }
    return result.reverse().join("");
}
複製代碼

總結

1.遞歸實現

優勢:實現代碼簡單,速度快

缺點:較大數據容易出現棧溢出,兼容性不夠友好

2.平方差實現

優勢:乘數計算少,速度快

缺點:兼容性不夠友好

3.數組存儲實現

優勢:數組清晰地存放大數據的每一個位數,每次計算時能簡單地從低位到高位求值

缺點:須要用數組存儲大數據的每位數,須要佔用較大內存,速度較慢

demo地址

相關文章
相關標籤/搜索