力扣每日一题0828-793. 阶乘函数后 K 个零

126 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

f(x) 是 x! 末尾是 0 的数量。回想一下 x! = 1 * 2 * 3 * ... * x,且 0! = 1 。

  • 例如, f(3) = 0 ,因为 3! = 6 的末尾没有 0 ;而 f(11) = 2 ,因为 11!= 39916800 末端有 2 个 0 。

给定 k,找出返回能满足 f(x) = k 的非负整数 x 的数量。

示例 1:

输入:k = 0
输出:5
解释:0!, 1!, 2!, 3!, 和 4! 均符合 k = 0 的条件。

示例 2:

输入:k = 5
输出:0
解释:没有匹配到这样的 x!,符合 k = 5 的条件。

示例 3:

输入: k = 3
输出: 5

二分查找

n! 尾零的数量即为 n!n! 中因子 1010 的个数,而 10=2×510=2\times 5,因此转换成求 n!n! 中质因子 22 的个数和质因子 55 的个数的较小值。

由于质因子 5 的个数不会大于质因子 2 的个数(具体证明见方法二),我们可以仅考虑质因子 5 的个数。

而 n! 中质因子 5 的个数等于 [1,n] 的每个数的质因子 5 的个数之和,我们可以通过遍历 [1,n] 的所有 5 的倍数求出。

优化计算

换一个角度考虑 [1,n] 中质因子 p 的个数。

[1,n] 中 p 的倍数有 n1=npn_1=\Big\lfloor\dfrac{n}{p}\Big\rfloor 个,这些数至少贡献出了 n1n_1 个质因子 p。p2p^2 的倍数有 n2=np2n_2=\Big\lfloor\dfrac{n}{p^2}\Big\rfloor 个,由于这些数已经是 p 的倍数了,为了不重复统计 p 的个数,我们仅考虑额外贡献的质因子个数,即这些数额外贡献了至少 n2n_2 个质因子 p。

代码实现

n5k=n5k15\Big\lfloor\dfrac{n}{5^k}\Big\rfloor=\Big\lfloor\dfrac{\Big\lfloor\dfrac{n}{5^{k-1}}\Big\rfloor}{5}\Big\rfloor

首先我们令 zeta(x)\textit{zeta}(x)x!x! 末尾零的个数。

zeta(x)=k=1x5kzeta(x)=∑^∞_{k=1}\Big\lfloor\dfrac{x}{5^k}\Big\rfloor

nxn_{x} 表示 x!x! 末尾零的个数不小于 xx 的最小数,那么题目等价于求解 nk+1nkn_{k + 1} - n_k

由于 zeta(x)\textit{zeta}(x) 为单调不减函数,因此 nk+1n_{k + 1}nkn_k 可以通过「二分查找」来求解。

又因为

zeta(x)=k=1x5kn5kzeta(x)=∑^∞_{k=1}\Big\lfloor\dfrac{x}{5^k}\Big\rfloor ≥ \Big\lfloor\dfrac{n}{5^k}\Big\rfloor

zeta(5x)xzeta(5x)≥x

所以当二分求解 nxn_{x} 时,我们可以将二分的初始右边界 r 设置为 5x。

var preimageSizeFZF = function(k) {
    return help(k + 1) - help(k);
}

const help = (k) => {
    let r = 5 * k;
    let l = 0;
    while (l <= r) {
        const mid = Math.floor((l + r) / 2);
        if (zeta(mid) < k) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return r + 1;
}

const zeta = (x) => {
    let res = 0;
    while (x != 0) {
        res += Math.floor(x / 5);
        x = Math.floor(x / 5);
    }
    return res;
};