《编程之美》学习笔记——2.2不要被阶乘吓倒

28 篇文章 0 订阅
13 篇文章 0 订阅

一、问题
  1、给定一个整数N,那么N的阶乘N!末尾有多少个0?

  例子:10!=3 628 800,则 N!的末尾有两个0。

  2、求N!的二进制表示中最低位1的位置。

二、解法——问题1

  个人思考:寻找数学规律

  首先,我们可以考虑的数学规律上去寻找末尾0的个数:对于个位数之间相乘能够给出0结尾的乘法有:5*(2/4/6/8),而二位数中10 = 2*5会给出0结尾,分析数学规律我们可以发现:

    对于输入N的阶乘,输出count与“5的数量”有关,15 = 3*5,特别的是25 = 5*5(两个);125 = 5*5*5(三个)50 = 5*10...,因此count = N / 10 * 2 + (N % 10) / 5 + N + [log5 N] * ([log5 N] + 1) / 2,但是log5 N 无法用库函数得到,因此这个系数我们可以通过不断对N除5求余得到。

算法C实现如下:

TYPE count_zero_of_factorial(TYPE N)
{
    assert(N >= 1 && N < 50);
    TYPE log_count = 0;
    TYPE temp = N;
    while ((temp /= 5) > 0) {
        log_count++;
    }
    DEBUG_PRINT_VALUE("%d", log_count);
    TYPE count =  (N / 10) * 2 + (N % 10) / 5 + log_count * (log_count - 1) / 2;
    return count;
}
  通过这个程序和其他版本同输入情况下测试输出可以发现本算法的BUG:上面考虑了25 = 5*5,但是没考虑50 = 5 * 10,150 = 15 * 10,算法是不完全的。实际测试时就发现当N >= 50就出错了。因此上述的算法只对1-49的输入会有正确解,其他都是错误的!算法本质错误之处是没有正确统计“5的数量”。

  版本一:因式分解

  它的思考角度也是:哪些数相乘能够得到10?但是给出了完整的数学证明:对N!= K* 10^M(且K不能被10整除)进行因式分解可以得到N!= 2^x * 3^y * 5^z * ...,由于10 = 2 * 5,所以 M = min(x, z)。实际上从2和5出现频率可以发现x > z,因此 M = z(即对N!进行因式分解后5出现的个数)。

算法C实现如下:

TYPE count_zero_of_factorial(TYPE N)
{
    assert(N >= 1);
    TYPE count = 0;
    TYPE i, j;
    /// count all value
    for (i = 1; i <= N; i++) {
        j = i;
        /// count factorization for number 5
        while (j % 5 == 0) {
            count++;
            j /= 5;
        }
    }
    return count;
}

  版本二:公式:Z=[N/5]+[N/5^2]+[N/5^3]……

  这个公式基于这么一个思路:是对于5来说,每个5,10,15,20,25,50等都能够各自先贡献一个5,可以由[N/5]直接求得;每个25,50,75,100,125等都能够在上一步基础上再贡献一个5,可以由[N/5^2]求得;每个125,250,375都能在上一步基础上再贡献一个5,可以由[N/5^3]求得,一直如此下去。总存在一个k使得5^k > N,此时[N/5^k]=0,运算截止。

  (《编程之美》英文原话:The reason this works is that a number ends in 0 if it is divisible by 10. Divisible by 10 means divisible by both 5 and 2. But there are lots of numbers divisible by 2 (half of them). So, we concentrate on the number of times a number is divisible by 5. But there are tricky numbers like 25, which are divisible by 5 twice, so we have to take those into account (or floor(n/25)). Then again, there are numbers that are divisible by 5 three times, like 125, so we have to take them into account.)
算法C实现如下:

TYPE count_zero_of_factorial(TYPE N)
{
    assert(N >= 1);
    TYPE count = 0;
    /// count by formula
    while (N /= 5) {
        count += N;
    }
    return count;
}

三、解法——问题2

  个人思考:转化问题求解。

  对问题:求N!的二进制表示中最低位1的位置,实际上求解的是N!的二进制表示中最低位开始到高位的连续的0的个数,可以把它转化为问题:求N!因式分解后质因数2的个数,若2的个数为k,则表示N!的二进制表示中最低位1的位置距离该数二进制表示中最低位的距离为k,或者在二进制中从最底位向最高位数起第(k+1)位。借助问题1中求因式分解方法和公式法,我们可以类比的解决这个问题。

  版本一:因式分解

算法C实现如下:

TYPE binary_1_position(TYPE N)
{
    assert(N >= 1);
    TYPE count = 0;
    TYPE i, j;
    /// count all value
    for (i = 1; i <= N; i++) {
        j = i;
        /// count factorization for number 2
        while ((j & 0x1) == 0) {
            count++;
            j >>= 1;
        }
    }
    return count;
}
  版本二:公式法 Z=[N/2]+[N/2^2]+[N/2^3]……

算法C实现如下:

TYPE binary_1_position(TYPE N)
{
    assert(N >= 1);
    TYPE count = 0;
    /// count by formula
    while (N >>= 1) {
        count += N;
    }
    return count;
}

  版本三:数学规律

  利用这么一个数学规律:N!含有的质因数2的个数,还等于N减去N的二进制表示中1的数目。

例子:(二进制表示)

N = 11011

Z = [N/2]+[N/2^2]+[N/2^3]…… = 1101 + 110 + 11 + 1

   = (1000 + 100 + 1) + (100 + 10) + (10 + 1) + 1 = (1000 + 100 + 10 + 1) + (100 + 10 + 1) + 1

   = 1111 + 111 + 1 (理解:针对N中某一位1,进行不断移位相加的结果必然是连续的1表示)

   = (10000 - 1) + (1000 - 1) + (10 - 1) + (1 - 1)(理解:上面进一步可表示为:N未移位前各自每一位非0位构成的二进制数减1并相加的结果)

   = 11011 - X = N - X(X为N二进制表示中1的个数)

  这样问题就继续转化为求解N二进制表示中1的个数,这个问题在《编程之美》前面题目中出现过,这里使用它的HAKMEM解法。

算法C实现如下:

TYPE count_number_1_in_byte(TYPE n)
{
    n = n - ((n >> 1) & 033333333333) -
        ((n >> 2) & 011111111111);
    n = (n + (n >> 3)) & 030707070707;
    return n % 63;
}

TYPE binary_1_position(TYPE N)
{
    assert(N >= 1);
    return N - count_number_1_in_byte(N);
}

四、思考题

  给定整数n,判断它是否是2的方幂。

  思考:如果它是2的方幂,那边n的二进制数表达中,它的最高位为1,低位均为0,我们只需要判断二进制表达式是否满足这种形式即可。可以通过判断它的二进制表达中1的个数是否为1来判断。2的方幂的表示中,0占据了大部分,只有一个1,根据这个数据的特点,我们可以利用 n &= n - 1这种形式去统计二进制表达中1的个数。当一个数是2的方幂时,恰好有(n & (n - 1)) == 0。

  编程之美给出的参考解答更加严格,限制了n必须是大于零的数:n > 0 && (n & (n-1)) == 0

备注:

1.转载请注明出处:http://blog.csdn.net/chensilly8888/

2.全文源码均开源(在UBUNTU + GCC4.8.2下编译并测试通过),可下载或查看:https://github.com/chenxilinsidney/funnycprogram/tree/master/beauty_of_programming/factorial

3.有写错或写漏之处请指正,谢谢!

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值