面试题之Javascript实现1万的阶乘
前言
最近面试的时候遇到一道面试题,就是实现10000!,当时的第一反应是直接用递归实现:
function factorial_recursion(n){ |
但是这样就会存在问题,Js中最大的安全整数为2^53- 1,10000!结果溢出该范围,代码运行结果为Infinity,无法计算出正确的结果。
那么如何才能计算大数据的阶乘呢?
BigInt
可以使用Js最新的基本数据类型BigInt,BigInt数据类型支持范围更大的整数值,可以解决整数溢出问题。
BigInt数据通过BigInt构造函数创建,修改代码如下:
function factorial_recursion(n){ |
通过factorial_recursion(10000)
就可以得出结果。
但当计算更高数值的阶乘时,比如求20000的阶乘,出现栈溢出的情况。
那么如何才能解决栈溢出问题?
平方差实现
算法思路
可以从减少乘法运算的次数角度出发,阶乘运算可转换为若干个平方差的积,使得阶乘只需要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
得出规律:24、21、16、9之间的差分别为基数3、5、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
得出规律:24、21、16、9之间的差分别为基数3、5、7,偶数基础乘数为 n / 2 * n
代码实现如下:
function factorial_square(n){ |
BigInt兼容性并不友好,Chrome浏览器在67+版本中才支持该数据类型。
在不支持BigInt的浏览器中怎么计算大数据阶乘呢?
数组存储实现
算法思路
可以使用数组来存储大数据结果的每位数,如result[0]存储个位数,result[1]存储十位数,以此类推。计算每位数时需要加上上一个位数得出的进位,最后再将数组反转并拼接,就可以得出大数据结果。
算法分析
以5! = 1 2 3 4 5为例:
代码实现
function factorial_array(n){ |
总结
1.递归实现
优点:实现代码简单,速度快
缺点:较大数据容易出现栈溢出,兼容性不够友好
2.平方差实现
优点:乘数计算少,速度快
缺点:兼容性不够友好
3.数组存储实现
优点:数组清晰地存放大数据的每个位数,每次计算时能简单地从低位到高位求值
缺点:需要用数组存储大数据的每位数,需要占用较大内存,速度较慢