[算法] 两个质数的乘积是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出现的次数的公式。

总结

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

速度对比那就更不用说了

展开阅读全文
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值