目录

0753:破解保险箱(2273 分)

力扣第 753 题

题目

有一个需要密码才能打开的保险箱。密码是 n 位数, 密码的每一位都是范围 [0, k - 1] 中的一个数字。

保险箱有一种特殊的密码校验方法,你可以随意输入密码序列,保险箱会自动记住 最后 n 位输入 ,如果匹配,则能够打开保险箱。

  • 例如,正确的密码是 "345" ,并且你输入的是 "012345"
    • 输入 0 之后,最后 3 位输入是 "0" ,不正确。
    • 输入 1 之后,最后 3 位输入是 "01" ,不正确。
    • 输入 2 之后,最后 3 位输入是 "012" ,不正确。
    • 输入 3 之后,最后 3 位输入是 "123" ,不正确。
    • 输入 4 之后,最后 3 位输入是 "234" ,不正确。
    • 输入 5 之后,最后 3 位输入是 "345" ,正确,打开保险箱。

在只知道密码位数 n 和范围边界 k 的前提下,请你找出并返回确保在输入的 某个时刻 能够打开保险箱的任一 最短 密码序列 。

示例 1:

输入:n = 1, k = 2
输出:"10"
解释:密码只有 1 位,所以输入每一位就可以。"01" 也能够确保打开保险箱。

示例 2:

输入:n = 2, k = 2
输出:"01100"
解释:对于每种可能的密码:
- "00" 从第 4 位开始输入。
- "01" 从第 1 位开始输入。
- "10" 从第 3 位开始输入。
- "11" 从第 2 位开始输入。
因此 "01100" 可以确保打开保险箱。"01100"、"10011" 和 "11001" 也可以确保打开保险箱。

提示:

  • 1 <= n <= 4
  • 1 <= k <= 10
  • 1 <= kn <= 4096

分析

  • 本题有个非常巧妙的转换方法:
    • 将所有 n-1 位数作为顶点 u
    • 对于 a 从 1 到 k-1,v=(u+a)[1:],添加一条边 u 指向 v,边为 a
    • 输入的密码序列即可看作是有向图中的一条路径
    • 要覆盖所有密码,等价于一条经过所有边的路径
  • 该图中所有顶点的入度和出度相同,因此是欧拉图,可以不重复地经过所有边
  • 因此用 Hierholzer 算法求欧拉回路即可

解答

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution:
    def crackSafe(self, n: int, k: int) -> str:
        def dfs(u):
            while g[u]<k:
                a = g[u]
                g[u] += 1
                dfs((u+str(a))[1:])
                res.append(str(a))
        
        g = defaultdict(int)
        res = []
        dfs('0'*(n-1))
        return ''.join(res)+'0'*(n-1)

3 ms

*附加

1
2
3
4
5
6
7
8
9
class Solution:
    def crackSafe(self, n: int, k: int) -> str:
        res = '0'*(n-1)
        g = defaultdict(lambda: k-1)
        for _ in range(k**n):
            u = res[len(res)+1-n:]
            res += str(g[u])
            g[u] -= 1
        return res

3 ms