百人開燈問題解法及優化
阿新 • • 發佈:2018-12-14
一:題意
房間裡有100盞電燈,編號為1,2,3……100,每盞燈上有一個按鈕,初始時燈全都是關的。編好號的100位同學由房間外依次走進去,將自己編號的倍數的燈的按鈕全部按一次
- 第一位同學把編號是1的倍數的燈的按鈕按一下(此時100盞燈全亮)
- 第二位同學把編號是2的倍數的燈的按鈕按一下(此時只有50盞燈亮著,50盞被這個人按滅了
- ……
- 第100位同學把編號是100的倍數的燈(即編號為100的燈)的按鈕按一下
請問依次走完後,還有多少盞燈亮著?
二:實現
1:暴力法
-
雙迴圈暴力破解
-
時間複雜度:O(n*n)
-
空間複雜度:O(1)
-
程式碼實現:
/** * 暴力法 * 時間複雜度:O(n*n) */ public class Test { public static void main(String[] args) { int[] light = new int[101]; //初始化為燈關閉 for (int i = 1; i < light.length; i++) { light[i] = 0; } //關燈操作 for (int i = 1; i < light.length; i++) { for (int j = 1; j < light.length; j++) { if(j%i == 0) { light[j] = light[j] == 0 ? 1 : 0; } } } //輸出開啟的燈 for (int i = 1; i < light.length; i++) { if(light[i] == 1) System.out.println(i); } /** * 輸出 * 1 * 4 * 9 * 16 * 25 * 36 * 49 * 64 * 81 * 100 */ } }
2:優化一:
-
觀察發現每一盞燈都只會被比自身小並且是自身約數的值和自身 進行開啟或者關閉操作
-
我們將自身數對燈的開啟關閉排除,則比這盞燈小並且是這盞燈約數的值為偶數,則最後為開燈,否則最後為熄燈
-
時間複雜度會比單純的暴力法降低一些
-
程式碼如下:
/** * 優化暴力法 * 時間複雜度:O(n*n),但會比單純的暴力法時間複雜度低 */ public class Test { public static void main(String[] args) { int[] light = new int[101]; //初始化為燈關閉 for (int i = 1; i < light.length; i++) { light[i] = 0; } //關燈操作 for (int i = 1; i < light.length; i++) { for (int j = 1; j < i; j++) { if(i%j == 0) { light[i]++; } } } //輸出開啟的燈 for (int i = 1; i < light.length; i++) { if(light[i]%2 == 0) System.out.println(i); } /** * 輸出 * 1 * 4 * 9 * 16 * 25 * 36 * 49 * 64 * 81 * 100 */ } }
3:優化二:
-
觀察發現,每盞燈都會被“1”操作一次,也會被自身的值操作一次,這兩次相互抵消
-
對於燈i,如果存在一個i的約數j,那麼一定存在另外一個約數k,如果j != k ,那麼這兩次相互抵消,如果j == k,那麼只有一個這樣的值,不會被抵消,這樣只有存在完全約數(比如:4 = 2*2 , 9 = 3*3類的)的燈才不會被抵消,所以我們要求的就是具有完全約數的值。
-
所以我們只要判斷值的開方是不是整數就可以了
-
時間複雜度:O(n)
-
沒有通用性,程式碼如下:
/** * 具體問題具體分析,沒有通用性 * 時間複雜度:O(n) */ public class Test { public static void main(String[] args) { int[] light = new int[101]; //初始化為燈關閉 for (int i = 1; i < light.length; i++) { light[i] = 0; } //關燈操作並輸出 for (int i = 1; i < light.length; i++) { double k = Math.sqrt(i); int m = (int)k; if((double)m == k) { System.out.println(i); } } /** * 輸出 * 1 * 4 * 9 * 16 * 25 * 36 * 49 * 64 * 81 * 100 */ } }