0793-阶乘函数后 K 个零

Raphael Liu Lv10

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

提示:

  • 0 <= k <= 109

方法一:二分查找

思路与算法

首先我们令 zeta}(x) 为 x! 末尾零的个数。根据「172. 阶乘后的零」的官方题解 ,有

zeta}(x) = \sum_{k = 1}^{\infty}\left\lfloor{x}{5^k}}\right\rfloor

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

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

又因为

\textit{zeta}(x) = \sum_{k = 1}^{\infty}\left\lfloor{x}{5^k}}\right\rfloor
\ge \left\lfloor{x}{5}}\right\rfloor

zeta}(5x) \ge x

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

代码

[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def preimageSizeFZF(self, k: int) -> int:
def zeta(n: int) -> int:
res = 0
while n:
n //= 5
res += n
return res

def nx(k: int) -> int:
return bisect_left(range(5 * k), k, key=zeta)

return nx(k + 1) - nx(k)
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
int zeta(long x) {
int res = 0;
while (x) {
res += x / 5;
x /= 5;
}
return res;
}

long long help(int k) {
long long r = 5LL * k;
long long l = 0;
while (l <= r) {
long long mid = (l + r) / 2;
if (zeta(mid) < k) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r + 1;
}

int preimageSizeFZF(int k) {
return help(k + 1) - help(k);
}
};
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public int preimageSizeFZF(int k) {
return (int) (help(k + 1) - help(k));
}

public long help(int k) {
long r = 5L * k;
long l = 0;
while (l <= r) {
long mid = (l + r) / 2;
if (zeta(mid) < k) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r + 1;
}

public long zeta(long x) {
long res = 0;
while (x != 0) {
res += x / 5;
x /= 5;
}
return res;
}
}
[sol1-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Solution {
public int PreimageSizeFZF(int k) {
return (int) (Help(k + 1) - Help(k));
}

public long Help(int k) {
long r = 5L * k;
long l = 0;
while (l <= r) {
long mid = (l + r) / 2;
if (Zeta(mid) < k) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r + 1;
}

public long Zeta(long x) {
long res = 0;
while (x != 0) {
res += x / 5;
x /= 5;
}
return res;
}
}
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
long long zeta(long x) {
long long res = 0;
while (x != 0) {
res += x / 5;
x /= 5;
}
return res;
}

long long help(int k) {
long long r = 5LL * k;
long long l = 0;
while (l <= r) {
long mid = (l + r) / 2;
if (zeta(mid) < k) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r + 1;
}

int preimageSizeFZF(int k){
return help(k + 1) - help(k);
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
};
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func zeta(n int) (res int) {
for n > 0 {
n /= 5
res += n
}
return
}

func nx(k int) int {
return sort.Search(5*k, func(x int) bool { return zeta(x) >= k })
}

func preimageSizeFZF(k int) int {
return nx(k+1) - nx(k)
}

复杂度分析

  • 时间复杂度:O(\log^2 k),其中 k 为题目给定数字,二分查找 n_{k + 1}, n_k 的时间复杂度为 O(\log k),其中每一步计算 zeta}(x) 的时间复杂度为 O(\log k)。
  • 空间复杂度:O(1),zeta}(x) 仅为常量空间开销。
 Comments
On this page
0793-阶乘函数后 K 个零