[演算法] 兩個質數的乘積是707829217,求解該質數

MapleShao發表於2019-04-25

原文連結:blog.csdn.net/shao941122/…

記錄一次有趣的演算法題。

44.jpeg

土生土長的北京妞兒,在衚衕里長大,房不多,就一個四合院和近郊的別墅。不算美如天仙但還算標緻,在清華讀的經管,現在在做基金經理(不想被人肉,就不暴露單位啦) ,個人擅長基本面分析,價值投資。現在只想找個聰明靠譜的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出現的次數的公式。

總結

我們通過 方案一 暴力破解方案二 公式法 來解決了這個問題。

速度對比那就更不用說了

相關文章