原文連結:blog.csdn.net/shao941122/…
記錄一次有趣的演算法題。
土生土長的北京妞兒,在衚衕里長大,房不多,就一個四合院和近郊的別墅。不算美如天仙但還算標緻,在清華讀的經管,現在在做基金經理(不想被人肉,就不暴露單位啦) ,個人擅長基本面分析,價值投資。現在只想找個聰明靠譜的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類中的replace
和contains
都是重量級方法。當我們使用有限次數時,並不會感覺到慢。但是當我們需要重複執行上億次時,就很慢了。
方式二 公式法
規律總結
- 對於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出現的次數的公式。
總結
我們通過 方案一 暴力破解 和 方案二 公式法 來解決了這個問題。
速度對比那就更不用說了