记录面试问题:尾递归实现阶乘 𝑛 !

63 阅读2分钟

阶乘 n! 有多种实现方式,既可以使用递归,也可以使用迭代、记忆化、闭包、尾递归、甚至异步方式。下面是 常见的7种实现方式(含思路):


✅ 1. 普通递归(经典写法)

function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}
  • 优点:代码简洁
  • 缺点:大数爆栈(Maximum call stack size exceeded)

✅ 2. 尾递归

📌 尾递归特点:

  • 最后一行是 return 调用自身
  • 没有多余的计算、回溯栈;
  • 参数 acc (accumulator)是累积结果。
function factorial(n, acc = 1) {
  if (n <= 1) return acc;
  return factorial(n - 1, n * acc);
}
  • 优点:理论上可以优化栈空间
  • 缺点:JavaScript 大多数环境不支持尾调用优化

✅ 3. 迭代 / 循环实现

function factorial(n) {
  let res = 1;
  for (let i = 2; i <= n; i++) {
    res *= i;
  }
  return res;
}
  • 优点:效率高、不爆栈
  • 缺点:结构不如递归清晰

✅ 4. 记忆化递归(Memoization)

const cache = {};
function factorial(n) {
  if (n <= 1) return 1;
  if (cache[n]) return cache[n];
  return cache[n] = n * factorial(n - 1);
}
  • 优点:避免重复计算
  • 缺点:空间换时间,递归仍可能爆栈

✅ 5. 闭包版(记忆化 + 闭包封装)

const factorial = (() => {
  const cache = {};
  return function f(n) {
    if (n <= 1) return 1;
    if (cache[n]) return cache[n];
    return cache[n] = n * f(n - 1);
  }
})();
  • 优点:数据私有化、封装性强
  • 缺点:递归栈依然存在问题

✅ 6. BigInt 支持的大数阶乘

function factorial(n) {
  let res = 1n;
  for (let i = 2n; i <= BigInt(n); i++) {
    res *= i;
  }
  return res;
}
  • 优点:支持计算超大阶乘(如 10000!)
  • 缺点:结果是 BigInt,不适用于浮点运算场景

✅ 7. 异步递归(用于学习 async/await)

async function factorial(n) {
  if (n <= 1) return 1;
  const prev = await factorial(n - 1);
  return n * prev;
}
  • 优点:理论上可避免同步爆栈(但没实质优化)
  • 缺点:性能低、无实际意义,更多用于 async 控制流学习

🚀 你可以根据用途选择不同实现:

场景推荐写法
小数计算普通递归 / 迭代
大数计算BigInt 迭代
面试优化尾递归 / 记忆化递归
数据封装闭包记忆化