记一个有趣的算法题:求n的阶乘最后一位非零数字

博客讨论了计算阶乘最后一位非零数字和末尾0个数的两种方法。第一种方法通过质因数分解,虽然正确但复杂度较高。第二种方法忽略了进位,导致错误。最后提出了修正后的第三种方法,通过单独处理质因子2和5,避免进位问题,实现了高效准确的计算。
摘要由CSDN通过智能技术生成

今天领导录题的时候问了一个题,使我陷入了迷茫。原题目是这样的:给出n,求n的阶乘的结果的最后一位非零数字,和末尾0的个位数。例如 n = 12 n=12 n=12, 则 n ! = 1 × 2 × . . . × 12 = 479001600 n!=1\times 2\times ... \times 12=479001600 n!=1×2×...×12=479001600,则其最后一位非零数字为6,末尾0的个数为2。

思路1(正确)

这个思路是领导刚问我时我首先想到的,但是是一个比较笨而且复杂度较高的方法。时间复杂度 O ( n ( l o g n ) 2 ) O(n(logn)^2) O(n(logn)2)

首先考虑到只有偶数乘5才回出现0,而大于2的偶数都是2的倍数,那我们只考虑 2 × 5 2\times 5 2×5就可以断定末尾0的个数。从而想到质因数分解,即1~n每个数都分解为质因数,同时把质因子2和5单独拿出来,其余质因子直接乘起来(模10乘即可,因为只需要个位数)。

提交到OJ验证该方法是正确的,耗时6毫秒。

#include <bits/stdc++.h>
using namespace std;

bool not_prime[1000005] = {false};

int main() {
    int n;
    cin >> n;
    int count2 = 0, count5 = 0;//记一下2  5的个数, 5肯定比2少
    int others_prod = 1;//记一下其他因子的积的个位数
    for (int i = 2; i <= n; i++) {
        if (not_prime[i])
            continue;
        for (int j = i; j <= n; j += i) {
            not_prime[j] = true;

            for (int temp = j; temp % i == 0; temp /= i) {  //分解出质因子i
                if (i == 2)
                    count2++;
                else if (i == 5)
                    count5++;
                else
                    others_prod = others_prod * i % 10;
            }
        }
    }
    for (int i = 0; i < count2 - count5; i++)  //5肯定比2少,2*5匹配完后,剩余的2乘回去
        others_prod = others_prod * 2 % 10;
    cout << others_prod << " " << count5 << endl;
}

思路2(错误)

在我还没有完善思路1的代码时,领导给我发了一个代码,我一看如此简单(比我当时想到的思路1简单多了),并一度觉得是正确的,思考 迷茫 了一中午,才想明白。想记录下来,于是有了本文。菜的发慌。

该思路将问题的【最后一位非零数字】和【末尾0的个数】分别计算。其中【末尾0的个数】可以直接质因子分解求出因子5的个数,因为末尾的0只能来自偶数乘5,而质因子2的个数肯定比5多,所以只统计质因子5即可。至于【最后一位非零数字】,在中间计算过程中,舍去末尾的0(因为0可以不参与乘法的计算,填在结果的末尾即可),然后通过模除10舍去高位,因为最后结果只要求最后一位非零数字。 于是代码如下,耗时2毫秒。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, i, temp;
    int ans1 = 1, ans5 = 0;

    cin >> n;
    for (i = 1; i <= n; i++) {
        ans1 = ans1 * i;
        while (ans1 % 10 == 0)
            ans1 /= 10;
        ans1 = ans1 % 10;

        temp = i;
        while (temp % 5 == 0) {
            ans5++;
            temp /= 5;
        }
    }

    cout << ans1 << " " << ans5;
    return 0;
}

我们举一个例子,按照上述代码运算,当 a n s 1 = 2 , i = 5 ans1=2,i=5 ans1=2,i=5时,最后一位非零数字为10,除10去0后是1。

对吗?

不对。

注意 a n s 1 ans1 ans1是上一轮乘法模除10得来的,虽然 a n s 1 = 2 ans1=2 ans1=2,但2只是个位数,其前面还有更多数字,如 a n s 1 ′ = 72 ans1'=72 ans1=72,那么 72 × 5 = 360 72\times 5=360 72×5=360,最后一位非零数字不再是1,而是6。

再如 a n s 1 = 5 , i = 8 ans1=5,i=8 ans1=5,i=8,最后一位非零数字是4,但如果上一轮乘法模除10之前 a n s 1 = 25 ans1=25 ans1=25,那么 25 ∗ 8 = 200 25*8=200 258=200,末尾非零数字是2。

什么原因呢?进位。在该代码中,是没有考虑到进位的,最简单的 2 × 5 2\times5 2×5要进一位, 125 ∗ 8 = 1000 125*8=1000 1258=1000要进三位,甚至还有进更多位的,所以该思路错误。那能不能针对原因改正改代码呢?见思路3。

思路3(正确)

验证了思路2的错误原因之后,有了最终的思路。时间复杂度 O ( n + m ) O(n+m) O(n+m),其中m是 n ! n! n!进行质因子分解后2的个数。
通过上文我们知道了其实只考虑质因子2和5的个数就可以了,所以我们在进行阶乘的过程中,把质因子2和5单独分离出来,其余因子直接乘起来。最后,显然2会比5多,所以有多少5, n ! n! n!结果末尾就会有多少个0,同时消耗掉对应数量的2,剩下的2再乘回总结果(product)即可。

耗时2毫秒

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;

    int count2 = 0, count5 = 0;  //记一下2,5的个数
    int product = 1;         //记一下其他质因子的积的个位数
    for (int i = 2; i <= n; i++) {
        int temp = i;
        for (; temp % 5 == 0; temp /= 5)
            count5++;
        for (; temp % 2 == 0; temp /= 2)
            count2++;
        product = product * temp % 10;
    }
    for (int i = 0; i < count2 - count5; i++)  //5肯定比2少,2*5匹配完后,剩余的2乘回去
        product = product * 2 % 10;
    cout << product << " " << count5 << endl;
}
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值