[LeetCode]阶乘函数后 K 个零

这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战

题目

793. 阶乘函数后 K 个零

解析

思考一下,乘法中如何获得一个10?

  • 要么是一个数本身就是10的倍数。
  • 要么就是5的倍数乘上2的倍数。

那么,带着这个思路来分析一下这个题目:

  • 既然是阶乘,那么乘上的数必然是连续的,也就是说:如果遇到了一个5的倍数,必然能找到一个2的倍数(且不是10的倍数),和这个5能凑成10的倍数;并且,后面的结果只会大于等于前面的结果。

也就是说:

  • 假设y=x+5,那么f(x) = f(x+1) = f(x+2) = f(x+3) = f(x+4) = f(y)-m,其中5^m^ = y.[1]

这个可以参考题目所给的:k = 5,结果为0,因为中间有一个25,因此不存在后面有5个连续0的情况。

那么,看题目所给的K,我们的函数为F,(对应的函数为W(x),即X的阶乘后有几个0)那么必然有:

  • F(K) = 0或F(K) = 5(根据【1】推知)

那么,如何推知在哪些K上的结果为0?

根据【1】,可以得知:

  • 在遇到某个可以拆出多个5的情况时,这个情况下W(x)-W(x-1)>1,那么W(x-1)到W(x)之间的这些k都是0。

而且:由于每个0都包含1个5,那么K个0必然有恰好K个5组合而成。

那么,问题就转化为了:

  • 是否可以找到某个数,到某个数为止的所有数恰好包含了K个5?如果包含了,结果就是5;如果没有,那么就是0.

且:5*K!中,必然包含了大于等于K个5。

那么,一个朴素的解法就是:从(5*K)!开始,每次做K-=5,来取是否有恰好W(x)=K。

因为可以得知:从K到K-5,有且仅有一次,F(X)会加上一个大于1的数;否则,都是+1;根据这个,我们可以在每个长度为5的区间里看看K是否会被包含在区间里。

那么,如何快速地判断W(x)可以参考下面的问题。

子问题:阶乘后的0

题目

leetcode-cn.com/problems/fa…

解析

按照上面的分析,对于任意x,我们都可以通过快速地将X除以5,来得知到x可以提取出多少个5,这些5就是从1到X中可以提取到的5的个数。

public static int trailingZeroes(int n) {
    if(n<5) return 0;
    return n/5 + trailingZeroes(n/5);
}
复制代码

那么就可以拟出最简单的答案:

public static int preimageSizeFZF(int k) {
    long y = 5*k;
    while(true){
        long m = trailingZeroes(y);
        if(m>k) {
            y -=5;
            continue;
        }
        if(m == k) return 5;
        if(m < k) return 0;
    }
}

public static long trailingZeroes(long n) {
    if(n<5) return 0;
    return n/5 + trailingZeroes(n/5);
}
复制代码

然后超时了。

最后执行的输入:

24397491

反思一下:

  • 在这个算法中,是否需要判断从0到5K的所有数?

    答案当然是否定的,那么如何确定下界?

    根据上面的trailingZeroes方法,可知:

    W(x) = sum(x/5 + x/25 + ... + 1),根据等比数列求和公式可知:

W(x) = x/5 * (1-(1/5)^n^)/(1-1/5) => W(x)>=x/4。

也就是说:下界其实就是4X:满足题目所给的数的大小,必然在4x到5x之间。

这样子只要最后y小于等于4x就可以知道不存在这么个数了(毕竟我们取的值必然是逼近4x但不会达到4x)。

并且,其实由于最终结果函数是一个随着n递增的函数,可以用二分法来确定这个数是否存在。

这里之所以可以使用二分法,原因就是:返回条件为5的情况是我们的值恰好满足条件

这里有个坑,java里int * long ,答案会按照int范围来取值,因此必须在乘的时候,让乘数都为long类型。

那么最终答案呼之欲出:

public static int preimageSizeFZF(int k) {
    long r = 5L*k,l = 4L*k+1L;
    while(l<=r){
       long mid = (l+r)/2;
       long n = trailingZeroes(mid);
       if(n == k) return 5;
       if(n > k) r = mid-1;
       else l = mid+1;
    }
    return trailingZeroes(l)==k?5:0;
}

public static long trailingZeroes(long n) {
    if(n<5) return 0;
    return n/5 + trailingZeroes(n/5);
}
复制代码

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:35.1 MB, 在所有 Java 提交中击败了69.55%的用户

猜你喜欢

转载自juejin.im/post/7061546288180887588