题目:
1. 给定一个整数N,那么N的阶乘N!末尾有多少个0?
2. 求N!的二进制表示中最低位1的位置。
分析与解答:
对于题目1:
最简单直接的方法,算出N!再看10的倍数关系。复杂度虽然不高,但致命的问题是溢出。在N不到100的时候就会引起溢出,所以虽然这是一个正确的方法,但在实际中却不能使用。这时就需要算法来解决问题,通过其他的途径而不是先要计算N!而求出最后的结果。
考虑N!末尾的0是怎么来的:对N!进行因式分解,N!=(2^X)*(3^Y)*(5^Z)*...,N!中因子2和因子5相乘可以得到10的倍数,最末尾有1个0。所以0的个数M=min(X,Z),因为可被2整除的数比5出现频率高而且可分解的2的数量多,所以X>Z,所以M=Z。所以最终的问题转化为,求N!因式分解结果中5的个数。
解法1和解法2都是基于该推理给出的。解法1,直接从1遍历到N,找出5的倍数,并将分解的数量相加。复杂度为Omiga(n)
int ret = 0; for(int i = 1; i <= N; i ++){ int j = i; while(j % 5 == 0){ ret ++; j /= 5; } }
解法2基于该思想,但有所改进,Z=[N/5] + [N/5^2] + [N/5^3] + ... ,即不大于N的数中,5的倍数贡献1个5,,5^2的倍数又贡献1个5,这样将所有贡献的5累加起来就是最后质因数分解后5的数量。复杂度为Theta(log5(n))。
int ret = 0; while(N){ ret += N / 5; N /= 5; }
对于题目2:
参考题目1,思考解题的本质。可以求,N!结果的二进制表示中末尾有多少个0,而二进制表示末尾如果是0,表示可以被2整除。所以该题其实是求N!中质因数2的数量。
这样可以使用题目1的解法2,求出该数。
int LowestOne(int N){ int ret = 0; while(N){ N >>= 1; //N缩小为原来的1/2,继续操作 ret += N; //N除以2的结果 } return ret + 1; }