题目描述
描述
二战时德军某部加密设备上有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;
}
}