Acho que a ideia principal é aproveitar o fato de não precisar recalcular tudo todas as vezes.
Por exemplo, vamos supor que eu já calculei 3! usando um loop (multiplicando todos os inteiros de 1 a 3). Depois, quando for calcular 4!, não preciso fazer todo o loop de 1 a 4. Eu posso fazer apenas 4\times 3! usando o resultado de 3! que já havia sido calculado anteriormente. E depois, se tiver um número maior (por exemplo, 8), eu posso multiplicar até o 5 (8\times 7\times 6\times 5) e depois multiplicar isso por 4! que já foi calculado.
Eu posso inclusive usar o próprio objeto final para isso. Pois ele está justamente armazenando os fatoriais que eu já calculei. Algo assim:
function fat(n, result) {
if (n in result) {
return;
}
var fat = n;
for (var i = n - 1; i > 1; i--) {
if (i in result) {
result[n] = fat * result[i];
return;
}
fat *= i;
}
result[n] = fat;
}
function calculeFactorial(...nums) {
var result = {};
for (var num of nums) {
fat(num, result);
}
return result;
}
console.log(calculeFactorial(3, 4, 5));
Assim só tem retrabalho quando os primeiros números são maiores. Por exemplo, se calcular primeiro 20!, somente este valor ficará em result. Depois, se tiver que calcular 4!, não há resultado anterior calculado para reaproveitar, então vai ter que fazer o loop de 1 a 4.
Então outra forma seria pré-calcular todos os fatoriais até o maior dos números, e depois retornar apenas os que precisar:
function calculeFactorial(...nums) {
var maior = Math.max(...nums);
var fatoriais = [ 1 , 1 ];
var fat = 1;
for (var i = 2; i <= maior; i++) {
fat *= i;
fatoriais[i] = fat;
}
var result = {};
for (var n of nums) {
result[n] = fatoriais[n];
}
return result;
}
Por exemplo, se os números forem 3, 4 e 5, primeiro ele vai calcular todos os fatoriais até 5, que é o maior valor de todos. Afinal, uma hora vai precisar fazer o loop até 5, então eu faço apenas uma vez, e aproveito para ir guardando os resultados intermediários. No final, fatoriais terá todos os fatoriais de 1 a 5.
Depois, basta pegar os fatoriais dos números que foram passados e retornar.
Fiz uns testes com o Benchmark.js e a primeira solução se mostrou mais rápida. Quando os primeiros números são maiores, a diferença diminui um pouco, por causa do que já foi dito (precisa recalcular os seguintes), mas ainda sim continua sendo mais rápida.