信號燈亮滅問題

題目描述

描述


二戰時德軍某部加密設備上有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盞燈存在的可能情況,共有2^{6}=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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章