来源:力扣(LeetCode)
描述:
有一个需要密码才能打开的保险箱。密码是 n
位数, 密码的每一位是 k 位序列 0, 1, ..., k-1
中的一个 。
你可以随意输入密码,保险箱会自动记住最后 n
位输入,如果匹配,则能够打开保险箱。
举个例子,假设密码是 "345"
,你可以输入 "012345"
来打开它,只是你输入了 6
个字符.
请返回一个能打开保险箱的最短字符串。
示例1:
输入: n = 1, k = 2
输出: "01"
说明: "10"也可以打开保险箱。
示例2:
输入: n = 2, k = 2
输出: "00110"
说明: "01100", "10011", "11001" 也能打开保险箱。
提示:
- n 的范围是 [1, 4]。
- k 的范围是 [1, 10]。
- kn 最大可能为 4096。
方法: Hierholzer 算法
Hierholzer 算法可以在一个欧拉图中找出欧拉回路。
具体地,我们将所有的 n − 1 位数作为节点,共有 kn−1 个节点,每个节点有 kk 条入边和出边。如果当前节点对应的数为 a1 a2 ⋯ an−1,那么它的第 x 条出边就连向数 a2 ⋯ an−1x 对应的节点。这样我们从一个节点顺着第 x 条边走到另一个节点,就相当于输入了数字 x。
在某个节点对应的数的末尾放上它的某条出边的编号,就形成了一个 n 位数,并且每个节点都能用这样的方式形成 k 个 n 位数。
例如 k = 4,n = 3 时,节点分别为 00, 01, 02, ⋯, 32, 33,每个节点的出边的编号分别为 0, 1, 2, 3,那么 00 和它的出边形成了 000, 001, 002, 003 这 4 个 3 位数,32 和它的出边形成了 320, 321, 322, 323 这 4 个 3 位数。
这样共计有 kn−1 × k = kn 个 n 位数,恰好就是所有可能的密码。
由于这个图的每个节点都有 k 条入边和出边,因此它一定存在一个欧拉回路,即可以从任意一个节点开始,一次性不重复地走完所有的边且回到该节点。因此,我们可以用 Hierholzer 算法找出这条欧拉回路:
设起始节点对应的数为 uu,欧拉回路中每条边的编号为 x1, x2, x3, ⋯,那么最终的字符串即为
Hierholzer 算法如下:
- 我们从节点 u 开始,任意地经过还未经过的边,直到我们「无路可走」。此时我们一定回到了节点 u,这是因为所有节点的入度和出度都相等。
- 回到节点 u 之后,我们得到了一条从 u 开始到 u 结束的回路,这条回路上仍然有些节点有未经过的出边。我么从某个这样的节点 v 开始,继续得到一条从 v 开始到 v 结束的回路,再嵌入之前的回路中,即
变为
以此类推,直到没有节点有未经过的出边,此时我们就找到了一条欧拉回路。
实际的代码编写具有一定的技巧性。
代码:
class Solution {
private:
unordered_set<int> seen;
string ans;
int highest;
int k;
public:
void dfs(int node) {
for (int x = 0; x < k; ++x) {
int nei = node * 10 + x;
if (!seen.count(nei)) {
seen.insert(nei);
dfs(nei % highest);
ans += (x + '0');
}
}
}
string crackSafe(int n, int k) {
highest = pow(10, n - 1);
this->k = k;
dfs(0);
ans += string(n - 1, '0');
return ans;
}
};
执行用时:4 ms, 在所有 C++ 提交中击败了86.44%的用户
内存消耗:9 MB, 在所有 C++ 提交中击败了30.51%的用户
复杂度分析
时间复杂度:O(n×kn )。
空间复杂度:O(n×kn )。
author:LeetCode-Solution