[算法] 两个质数的乘积是707829217,求解该质数

记录一次有趣的算法题。
在这里插入图片描述

土生土长的北京妞儿,在胡同里长大,房不多,就一个四合院和近郊的别墅。不算美如天仙但还算标致,在清华读的经管,现在在做基金经理(不想被人肉,就不暴露单位啦) ,个人擅长基本面分析,价值投资。现在只想找个聪明靠谱的IT男。硬性要求是年龄,不要超过88年,还有不要特别矮或胖。我对智商的要求比较高,下面就出个题测试下。

我的微信ID是大写字母NY后面跟着两个质数,大的在前,小的在后,乘积是707829217,可直接搜索添加,另外还有个附加题目,在刚刚微信ID的数字中,从1开始到这个数字的奇数序列里,一共出现了多少个3,如果私信我正确的答案,我将直接邀你见面!期待缘分降临~

问题1 求解微信号

	// 两个质数的乘积是707829217,求质数
	int num = 707829217;
	int i = 1;
	while (i <= num) {
		i += 2;
		if (num % i == 0) {
			System.out.println("发现: " + num + " / " + i + " = " + (num / i));
		}
	}

打印结果:

发现: 707829217 / 8171 = 86627
发现: 707829217 / 86627 = 8171
发现: 707829217 / 707829217 = 1

Process finished with exit code 0

所以我们得到第一问的答案:NY866278171

问题2 求解奇数序列中,3出现的次数

我们看到这个数值866278171,为8亿多,去掉偶数,只看奇数,也有4亿多。

问这4亿个数中3出现了多少次,这个问题有点费解。

方式一 暴力破解

所谓暴力破解,就是遍历每一个数值,统计3出现的次数。下面的各个版本仅供参考:

该方案耗时:2m 52s 139ms

        // 奇数序列中,一共出现了多少次3
        int number = 866278171;
        int sum = 0;
        for (int i = 1; i <= number; i = i + 2) {
            sum += String.valueOf(i).replace("3", "_#_")
                    .split("#")
                    .length - 1;
        }
        // 总数: 441684627
        System.out.println("总数: " + sum);

该方案耗时:1m 41s 259ms

        // 奇数序列中,一共出现了多少次3
        int number = 866278171;
        int sum = 0;
        for (int i = 1; i <= number; i = i + 2) {
            String str = String.valueOf(i);
            if (str.contains("3")) {
                sum += str.length() - str.replace("3", "").length();
            }
        }
        // 总数: 441684627
        System.out.println("总数: " + sum);

该方案耗时:22s 942ms

        // 奇数序列中,一共出现了多少次3
        int number = 866278171;
        int sum = 0;
        for (int i = 1; i <= number; i = i + 2) {
            String str = String.valueOf(i);
            for (int j = 0; j < str.length(); j++) {
                if (str.charAt(j) == '3') {
                    sum++;
                }
            }
        }
        // 总数: 441684627
        System.out.println("总数: " + sum);

该方案耗时:6s 669ms

        // 奇数序列中,一共出现了多少次3
        int number = 866278171;
        int sum = 0;
        for (int i = 1; i <= number; i = i + 2) {
            int k = i;
            while (k > 1) {
                if (k % 10 == 3) {
                    sum++;
                }
                k /= 10;
            }
        }
        // 总数: 441684627
        System.out.println("总数: " + sum);

我们看到,走了好多的弯路,String类中的replacecontains都是重量级方法。当我们使用有限次数时,并不会感觉到慢。但是当我们需要重复执行上亿次时,就很慢了。

方式二 公式法

规律总结
  • 对于1位数
3只出现1次。
  • 对于2位数:
3出现在个位数,十位数可以任意0-9,有10种。33暂时算一次
3出现在十位数,个位数可以任意0-9,有10种,33暂时算一次,加上上一次的补齐
所以,对于任意两位数,3出现了20次。
  • 对于3位数:
3出现在个位数,十位数、百位数 可以任意00-99,有100种。 x33、3x3、333暂时算一次
3出现在十位数,个位数、百位数 可以任意00-99,有100种。 x33、33x、333暂时算一次 
3出现在百位数,个位数、十位数 可以任意00-99,有100种。 3x3、33x、333暂时算一次 
少算的,都补齐了,所以,对于任意3位数,3出现了300次。
  • 对于4位数:
3出现在个位数,十位数、百位数、千位数 可以任意000-999,有1000种。
xx33、x3x3、3xx3、x333、33x3、3x33、3333暂时算一次
3出现在十位数,个位数、百位数、千位数 可以任意000-999,有1000种。 
xx33、x33x、3x3x、x333、3x33、333x、3333暂时算一次
3出现在百位数,个位数、十位数、千位数 可以任意000-999,有1000种。 
x3x3、x33x、33xx、x333、33x3、333x、3333暂时算一次
3出现在千位数,个位数、十位数、百位数 可以任意000-999,有1000种。 
3xx3、3x3x、33xx、3x33、33x3、333x、3333暂时算一次
少算的,都补齐了,所以,对于任意4位数,3出现了4000次。

好像有点规律了。。

对于任意N位数,3出现的次数为 n * 10^(n-1)

翻译成代码:

    /**
     * 任意N位数,3出现的次数
     */
    public double anyN(int n) {
        if (n < 1)
            return 0;
        return n * Math.pow(10, n - 1);
    }

问题来了,对于一个有限度的N位数,3出现了多少次?

比如: 0 ~ 2918,3出现了多少次?

拆分下:
0 ~ 2000区间段,
可以理解为2个1000,也就是2个任意3位数,所以 :2 * 300 + 0 (对于任意3位数3出现的次数为300,不包含3000~3999 整个以3开头的千位数)
2000 ~ 2900区间段,
可以理解为9个100,也就是9个任意2位数,所以:9 * 20 + 100( 任意2位数3出现的次数为20,包含300-399 整个以3开头的百位数)
2900 ~ 2910区间段,
可以理解为1个10,也就是1个任意1位数,所以:1 * 1 + 0( 任意1位数3出现的次数为1,不包含30-39 整个以3开头的十位数)
2910 ~ 2918区间段,
只看个位数,0 ~ 8,包含一个3,所以: 1
综合起来就是:
(2 * 300 + 0)+(9 * 20 + 100) + (1 * 1 + 0)  + (1)= 600+280+1+1 = 882

我们拆开翻译,

  • 步骤1

对于0 ~ n * 10^w 的数,3出现了多少次。
比如0~100,0~4000,0~800000000

    /**
     * 计算一个 [ 0 , n*10^w ) 的数中,3出现的次数
     * <p>
     * e.g:  4000  n = 4 , w = 3
     *
     * @param n 数值
     * @param w 0的个数
     * @return
     */
    public int count3(int n, int w) {
        // n * 10^(w-1) + 10^w
        double sum = n * anyN(w);
        if (n > 3) {
            sum += Math.pow(10, w);
        }
        return (int) sum;
    }
  • 步骤2

对于任意0 ~ N, 3出现了多少次

    /**
     * 计算0~N的数中,3出现的次数
     */
    public int anyNumCount3(int anyN) {
        double sum = 0;
        int number = anyN;
        int count0 = 0;
        while (number > 1) {
            int n = number % 10;
            sum += count3(n, count0);
            if (n == 3) {
                // 如果该位为3,需要将低位数再加一遍。
                // 比如 389,[300,389]共89+1=90个
                sum += (anyN % Math.pow(10, count0)) + 1;
            }
            number /= 10;
            count0++;
        }
        return (int) sum;
    }

该方案耗时:1ms ?

至此,我们通过找规律,发现了对于[0~N]中3出现的次数的公式。

只看奇数序列

针对本题的只看奇数序列,我们总结下规律:

奇数,也就是限定了个位数只能是1、3、5、7、9共5种选择,而更高位可以是0-9共十种选择。

对于任意N位奇数
  • 对于1位数
3只出现1次。
  • 对于2位数:
3出现在个位数,十位数可以任意0-9,有10种。33暂时算一次
3出现在十位数,个位数可以是13579,有5种,33暂时算一次,加上上一次的补齐
所以,对于任意两位数,3出现了15次。
  • 对于3位数:
3出现在个位数,十位数0-9、百位数0-9,有10*10=100种。 x33、3x3、333暂时算一次
3出现在十位数,百位数0-9,个位13579,有10*5=50种。 x33、33x、333暂时算一次 
3出现在百位数,十位数0-9,个位13579,有10*5=50种。 3x3、33x、333暂时算一次 
少算的,都补齐了,所以,对于任意3位数,3出现了200次。
  • 对于4位数:
3出现在个位数,十、百、千位数 可以任意0-9,有1000种。
xx33、x3x3、3xx3、x333、33x3、3x33、3333暂时算一次
3出现在十位数,个位13579、百、千位数 可以任意0-9,有500种。 
xx33、x33x、3x3x、x333、3x33、333x、3333暂时算一次
3出现在百位数,个位13579、十、千位数 可以任意0-9,有500种。 
x3x3、x33x、33xx、x333、33x3、333x、3333暂时算一次
3出现在千位数,个位13579、十、百位数 可以任意0-9,有500种。 
3xx3、3x3x、33xx、3x33、33x3、333x、3333暂时算一次
少算的,都补齐了,所以,对于任意4位数,3出现了2500次。

规律:对于任意N位数,只看奇数,3出现的次数为1*10^(n-1) + (n-1)*5*10^(n-2)

翻译成代码:

    /**
     * 任意N位奇数,3出现的次数
     * e.g: 9999 n = 4
     */
    public int anySingleN(int n) {
        // 1*10^3 + 3*5*10^2
        if (n < 1) return 0;
        double sum = Math.pow(10, n - 1);
        if (n >= 2) {
            sum += (n - 1) * 5 * Math.pow(10, n - 2);
        }
        return (int) sum;
    }
对于有限制的N位奇数,3出现的次数
  • 比如:4000以内的奇数

4个任意三位奇数 + 3开头的,任意4位奇数。即4*anySingleN(3) + 10*10*5

翻译成代码:

    /**
     * 计算一个 [ 0 , n*10^w ) 的奇数中,3出现的次数
     * <p>
     * e.g:  4000  n = 4 , w = 3
     *
     * @param n 数值
     * @param w 0的个数
     * @return
     */
    public int countSingle3(int n, int w) {
        // 4 * anySingleN(3) + 5*10^2
        if (w < 1)// 个位数
            return (n >= 3) ? 1 : 0;
        double sum = n * anySingleN(w);
        if (n > 3) {
            sum += 5 * Math.pow(10, w - 1);
        }
        return (int) sum;
    }
  • 对于0~N的任意奇数中,3出现的次数
    /**
     * 计算0~N的奇数数中,3出现的次数
     */
    public int anySingleNumCount3(int anyN) {
        int sum = 0;
        int number = anyN;
        int count0 = 0;
        while (number > 0) {
            int n = number % 10;
            sum += countSingle3(n, count0);
            if (n == 3) {
                // 如果该位为3,需要将低位奇数再加一遍
                // 比如 389,[300-389]共(89+1)/2 = 50个
                sum += (anyN % Math.pow(10, count0) + 1) / 2;
            }
            number /= 10;
            count0++;
        }
        return (int) sum;
    }

该方案耗时:1ms ?

至此,我们通过找规律,发现了对于[0,N]奇数序列中3出现的次数的公式。

总结

我们通过 方案一 暴力破解方案二 公式法 来解决了这个问题。

速度对比那就更不用说了

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
RSA算法是一种非对称加密算法,用于实现数据加密和数字签名等功能。它基于两个大素数的乘积作为公钥,私钥则由这两个素数的乘积及一些其他参数计算得出。在RSA算法中,模逆算法是非常重要的一部分,它用于计算一个数的模逆。 以下是用C语言实现RSA算法的代码,包括模逆算法: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #include <math.h> // 求a和b的最大公约数 int gcd(int a, int b) { if (b == 0) return a; return gcd(b, a % b); } // 求a和b的最小公倍数 int lcm(int a, int b) { return (a * b) / gcd(a, b); } // 判断一个数是否为素数 int is_prime(int n) { int i, flag = 1; for (i = 2; i <= sqrt(n); i++) { if (n % i == 0) { flag = 0; break; } } return flag; } // 生成随机素数 int get_random_prime(int min, int max) { int i, p; for (i = 0; i < 100; i++) { p = rand() % (max - min + 1) + min; if (is_prime(p)) return p; } return -1; } // 求模逆 int mod_inverse(int a, int n) { int t, r, t1 = 0, t2 = 1, r1 = n, r2 = a; while (r2 != 0) { t = r1 / r2; r = r1 - t * r2; r1 = r2; r2 = r; t = t1 - t * t2; t1 = t2; t2 = t; } if (r1 > 1) return -1; if (t1 < 0) t1 += n; return t1; } // RSA加密 int rsa_encrypt(int m, int e, int n) { int c = 1, i; for (i = 0; i < e; i++) { c *= m; c %= n; } return c; } // RSA解密 int rsa_decrypt(int c, int d, int n) { int m = 1, i; for (i = 0; i < d; i++) { m *= c; m %= n; } return m; } int main() { srand(time(NULL)); int p, q, n, fn, e, d, m, c; printf("输入明文m:"); scanf("%d", &m); do { p = get_random_prime(100, 1000); q = get_random_prime(100, 1000); n = p * q; fn = lcm(p - 1, q - 1); e = rand() % (fn - 2) + 2; } while (gcd(e, fn) != 1); d = mod_inverse(e, fn); c = rsa_encrypt(m, e, n); printf("p=%d, q=%d, n=%d, fn=%d, e=%d, d=%d, c=%d\n", p, q, n, fn, e, d, c); m = rsa_decrypt(c, d, n); printf("解密后的明文m:%d\n", m); return 0; } ``` 在上述代码中,我们使用了求最大公约数、求最小公倍数、判断素数、求模逆、RSA加密和RSA解密等函数。其中,求模逆算法使用了扩展欧几算法,用于求解a在模n下的逆元x,即满足ax ≡ 1 (mod n)的x。 需要注意的是,在实际应用中,通常会选择更大的素数p和q来保证加密的安全性。同时,为了避免破解,还需要在选择素数时进行一定的随机化处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值