There is a box protected by a password. The password is n
digits, where each letter can be one of the first k
digits 0, 1, ..., k-1
.
You can keep inputting the password, the password will automatically be matched against the last n
digits entered.
For example, assuming the password is "345"
, I can open it when I type "012345"
, but I enter a total of 6 digits.
Please return any string of minimum length that is guaranteed to open the box after the entire string is inputted.
Example 1:
Input: n = 1, k = 2 Output: "01" Note: "10" will be accepted too.
Example 2:
Input: n = 2, k = 2 Output: "00110" Note: "01100", "10011", "11001" will be accepted too.
Note:
n
will be in the range[1, 4]
.k
will be in the range[1, 10]
.k^n
will be at most4096
.
思路:https://www.youtube.com/watch?v=iPLQgXUiU14
欧拉回路,dfs,关键是重复利用前n-1位
Approach #1: Euler Circuit [Accepted]
Intuition
We can think of this problem as the problem of finding an Euler path (a path visiting every edge exactly once) on the following graph: there are kn−1 nodes with each node having k edges.
For example, when k = 4, n = 3
, the nodes are '00', '01', '02', ..., '32', '33'
and each node has 4 edges '0', '1', '2', '3'
. A node plus edge represents a complete edge and viewing that substring in our answer.
Because our graph is highly connected and symmetric, we should expect intuitively that taking any path greedily in some order will probably result in an Euler path. Actually, this intuition is also a theorem: any connected directed graph where all nodes have equal in-degree and out-degree has an Euler circuit (an Euler path ending where it started.)
Algorithm
We will modify our standard depth-first search: instead of keeping track of nodes, we keep track of (complete) edges: seen
records if an edge has been visited.
Also, we'll need to visit in a sort of "post-order", recording the answer after visiting the edge. This is to prevent deadends. For example, with k = 2, n = 2
, we have the nodes '0', '1'
. If we greedily visit complete edges '00', '01', '10'
, we will be stuck. However, if we visit in post-order, we'll end up visiting '00', '01', '11', '10'
correctly.
In general, because of the situation where taking some edge will make us stuck, we should try to travel and visit other edges first.
Complexity Analysis
-
Time Complexity: O(n∗kn). We visit every edge once in our depth-first search, and nodes take O(n)space.
-
Space Complexity: O(n∗kn), the size of
seen
.
class Solution(object):
def crackSafe(n, k):
seen = set()
ans = []
def dfs(node):
for x in map(str, range(k)):
nei = node + x
if nei not in seen:
seen.add(nei)
dfs(nei[1:])
ans.append(x)
dfs("0" * (n-1))
return "".join(ans) + "0" * (n-1)
Approach #2: Inverse Burrows-Wheeler Transform [Accepted]
Explanation
If we are familiar with the theory of combinatorics on words, recall that a Lyndon Word L
is a word that is the unique minimum of it's rotations.
One important mathematical result (due to Fredericksen and Maiorana), is that the concatenation in lexicographic order of Lyndon words with length dividing n
, forms a de Bruijin sequence: a sequence where every every word (from the kn available) appears as a substring of length n
(where we are allowed to wrap around.)
For example, when n = 6, k = 2
, all the Lyndon words with length dividing n
in lexicographic order are (spaces for convenience): 0 000001 000011 000101 000111 001 001011 001101 001111 01 010111 011 011111 1
. It turns out this is the smallest de Bruijin sequence.
We can use the Inverse Burrows-Wheeler Transform (IBWT) to generate these Lyndon words. Consider two sequences: S
is the alphabet repeated kn−1 times: S = 0123...0123...0123....
, and S'
is the alphabet repeated kn−1 times for each letter: S' = 00...0011...1122....
We can think of S'
and S
as defining a permutation, where the j
-th occurrence of each letter of the alphabet in S'
maps to the corresponding j
-th occurrence in S
. The cycles of this permutation turn out to be the corresponding smallest de Bruijin sequence (link).
Under this view, the permutation S′→S [mapping permutation indices (i∗kn−1+q)→(q∗k+i)] form the desired Lyndon words.
Complexity Analysis
-
Time Complexity: O(kn). We loop through every possible substring.
-
Space Complexity: O(kn), the size of
P
andans
.
class Solution(object):
def crackSafe(self, n, k):
M = k**(n-1)
P = [q*k+i for i in xrange(k) for q in xrange(M)]
ans = []
for i in xrange(k**n):
j = i
while P[j] >= 0:
ans.append(str(j / M))
P[j], j = -1, P[j]
return "".join(ans) + "0" * (n-1)
class Solution(object):
def crackSafe(self, n, k):
"""
:type n: int
:type k: int
:rtype: str
"""
ans = "0" * (n - 1)
visits = set()
for x in range(k ** n):
current = ans[-n+1:] if n > 1 else ''
for y in range(k - 1, -1, -1):
if current + str(y) not in visits:
visits.add(current + str(y))
ans += str(y)
break
return ans
def crackSafe(self, n, k):
s = '0' * (n - 1)
D = '9876543210'[-k:]
for _ in range(k**n):
s += next(d for d in D if (s + d)[-n:] not in s)
return s