题目地址:
https://leetcode.com/problems/cracking-the-safe/
给定一个正整数 n n n和一个正整数 k k k,题目保证 k < 10 k<10 k<10,要求给出一个序列 s s s,只含 0 ∼ k − 1 0\sim k-1 0∼k−1,使得任意长 n n n的只由 0 ∼ k − 1 0\sim k-1 0∼k−1组成的序列都是 s s s的子串。返回任一符合要求的答案即可。
其实是欧拉回路问题。一共有 k n k^n kn种可能的序列,首先理论上,如果我们能使得,除了前 n − 1 n-1 n−1位长度不够,但是从第 n n n位开始,后面每次添加一个数字,得到的长 n n n后缀都是一种子串,并且每次添加完一个数字后都能得到不同的子串,那么这样得出的序列 s s s一定是最短的,并且不可能更短了。这样的 s s s的长度是 n − 1 + k n n-1+k^n n−1+kn。我们看能否构造出这个 s s s。
现在将所有只含
0
∼
k
−
1
0\sim k-1
0∼k−1的长
n
−
1
n-1
n−1的字符串看成是顶点,比如
u
u
u和
v
v
v是两个长
n
−
1
n-1
n−1的字符串,并且存在边
u
→
v
u\to v
u→v当且仅当
u
u
u的长
n
−
2
n-2
n−2的后缀等于
v
v
v的长
n
−
2
n-2
n−2的前缀(
u
→
v
u\to v
u→v可以是自环。这里的意思是将所有长
n
n
n的字符串看成边,该边可以用
u
u
u字符串append上
v
v
v的最后一个字符得到)。首先,这个有向图一定含欧拉回路。第一,其每个顶点后面可以加
k
k
k个不同字符,前面也可以加
k
k
k个不同字符,所以每个顶点入度等于出度;第二,任意两个点之间一定是连通的,例如对于"abc"
要走到"xyz"
,可以"abc" -> "bcx" -> "cxy" -> "xyz"
,其余情况可以类似做。所以这个图是个欧拉图,从而从任一点出发都存在欧拉回路,由于欧拉回路里每条边只走一次,所以每走一条边就能得到一个全新的长
n
n
n的
0
∼
k
−
1
0\sim k-1
0∼k−1的排列。随便取一个出发点,设这个点是字符串
t
t
t,每走一条边,就将该边的最后一个字符append到
t
t
t后面,走完欧拉回路之后形成的字符串就是答案。代码如下:
import java.util.HashSet;
import java.util.Set;
public class Solution {
public String crackSafe(int n, int k) {
String start = "0".repeat(n - 1);
StringBuilder sb = new StringBuilder();
dfs(start, new HashSet<>(), k, sb);
// 最后可以不用翻转,因为任意答案的翻转肯定也是答案,是左右对称的
return sb + start;
}
private void dfs(String s, Set<String> set, int k, StringBuilder sb) {
for (int i = 0; i < k; i++) {
// 枚举所有出边
String next = s + i;
// 如果该边没走过,则走之
if (set.add(next)) {
// 下一个点是next[1:]
dfs(next.substring(1), set, k, sb);
sb.append(i);
}
}
}
}
时空复杂度 O ( n + k n ) O(n+k^n) O(n+kn)。