題目描述
描述
二戰時德軍某部加密設備上有8個一組的信號燈,這些信號燈的亮滅用於初始化加密機的密鑰。
這些信號燈每秒鐘變化一次,並且燈的某實可狀態完全由上一秒鐘燈組的狀態決定,具體的規則爲:
如果一個信號燈左右相鄰的燈都亮,或者都滅,則下一秒該燈亮。其他情況下,信號燈都滅。(特別地,比如兩邊的燈,他們只有一個相鄰燈)
這裏我們以字符串ON代表燈亮,以字符串OFF代表燈滅。給定一組燈初始狀態,以及經過的時間秒數s,求s秒後每個燈的亮滅情況。
注意,需考慮s非常大的情況,全遍歷(時間複雜度O(n))算法通過的用例很少。
輸入描述
燈最開始的狀態
OFF,OFF,OFF,OFF,ON,ON,ON,OFF,OFF
經過的時間
5
輸出描述
燈經過5秒後的狀態:
OFF,OFF,ON,ON,OFF,OFF,OFF,OFF
解釋:
根據規則
1秒後燈的狀態OFF,ON,ON,OFF,OFF,ON,OFF,OFF
2秒後燈的狀態OFF,OFF,OFF,OFF,OFF,ON,OFF,OFF
3秒後燈的狀態OFF,ON,ON,ON,OFF,ON,OFF,OFF
4秒後燈的狀態OFF,OFF,ON,OFF,ON,ON,OFF,OFF
5秒後燈的狀態OFF,OFF,ON,ON,OFF,OFF,OFF,OFF
題目分析
初步分析
題目中說明了全遍歷通過率低,那麼我們放棄暴力破解,分析一下題目。
由題意可知,兩頭的燈永遠是滅的,那麼8盞燈存在的可能情況,共有=64種情況。這裏用1、0表示燈的亮滅。
在燈的變化中,由A狀態一定會變爲B狀態,B變爲C,如果在變化中,A再次出現,那麼 A -> B -> C -> …… -> A 組成了一個循環。
例如 0 1 1 0 (允許我用少一點的分析,清晰一些),1~2秒構成了一個循環。
0秒後 0 1 1 0
1秒後 0 0 0 0
2秒後 0 1 1 0
這樣可以用動態規劃的思想,把出現過的情況都存儲起來,最壞情況下會出現64種。此時我們的複雜度爲O(1)。
後續分析
然後我們再仔細分析一下,會不會出現 A -> B -> C -> D -> …… -> Z -> C 的情況,即 C -> Z 組成了一個循環,A、B不在循環內呢。
當然是有可能的啦,比如這個例子 0 0 1 1 0 。
0秒後 0 0 1 1 0
1秒後 0 0 0 0 0
2秒後 0 1 1 1 0
3秒後 0 0 1 0 0
4秒後 0 0 1 0 0
我們可以看到,3秒之後 0 0 1 0 0形成了一個循環,而0~2秒的狀態不進入循環。所以這種狀態是需要考慮滴。
詳細分析
假設位置 j 開始循環,位置 i-1 結束循環,即 i 與 j 位置的字符串相同
那麼
不計入循環 : 0 ~ ( j - 1) 長度 j
循環體: j ~ ( i - 1) 長度 i - j
S 秒後的位置 : ( s - j ) % ( i - j ) + j
描述: 先減去不計入循環的 除以循環長度 加上循環開始位置
代碼
import java.util.HashMap;
public class LightEncode {
public static void main(String[] args) {
String str = "OFF,OFF,OFF,OFF,ON,ON,ON,OFF,OFF";
System.out.println(test1(str, 5));
System.out.println(test2(str, 5));
}
private static String test1(String str, Integer s) {
String tmp = encode(str);
//記錄key-value
HashMap<Integer, String> map1 = new HashMap<>();
HashMap<String, Integer> map2 = new HashMap<>();
int i = 1;
map1.put(0, tmp);
map2.put(tmp, 0);
for (i = 1; i < s; i++) {
// System.out.println(decode(tmp));
tmp = getNext(tmp);
if (map2.containsKey(tmp)) {
// 在 i 出現了重複的字符串,所以 i-1爲循環結束位置 ------------- i-1 循環結束位置
break;
} else {
map1.put(i, tmp);
map2.put(tmp, i);
}
}
if (i==s) {
//由於是從1開始的,少跑了一趟,在這裏補上
return decode(getNext(tmp));
} else {
// 開始循環的位置 j --------------- j 循環開始位置
Integer j = map2.get(tmp);
// 不計入循環 0 ~ (j-1) 長度 j
// 循環 j ~ (i-1) 長度 i-j
// s秒後的位置 -> (s - j) % (i - j) + j
// ^ ^ ^
// | | |
// 先減去不計入循環的 除以循環長度 加上循環開始位置
return decode(map1.get((s-j) % (i-j) + j));
}
}
private static String test2(String str, Integer s) {
String tmp = encode(str);
int i = 0;
for (i = 0; i < s; i++) {
System.out.println(decode(tmp));
tmp = getNext(tmp);
}
return decode(tmp);
}
/**
* 計算下個序列
* @param str
* @return
*/
private static String getNext(String str) {
String res = "0";
for (int i = 1; i < 7; i++) {
if (str.charAt(i-1) == str.charAt(i+1)) {
res = res + "1";
} else {
res = res + "0";
}
}
return res + "0";
}
private static String encode(String str) {
String tmp = str.replaceAll("ON", "1").replaceAll("OFF", "0").replace(",", "");
return tmp;
}
private static String decode(String str) {
String tmp = str.replaceAll("0", "OFF,").replaceAll("1", "ON,");
tmp = tmp.substring(0, tmp.length()-1);
return tmp;
}
}