1、题干
有一个需要密码才能打开的保险箱。密码是 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 <= 41 <= k <= 101 <= kn <= 4096
2、思路1
- 题意
题目要找一个能打开保险箱的最短字符串,实际是想找一个所有密码组合而成的最短字符串,该字符串的特征是:截取其中任意长度为 n 的字符串都是保险箱的密码。
- 思路
先找出所有密码串,再用所有密码串组合出最短字符串。
找出所有密码时要注意,任一密码串长度都为 n,每一位的取值范围是 。
组合最短字符串时要注意,在组合任意两个密码串 a 和 b 时,必须满足 a 剔除首位字符后与 b 剔除末位字符后相等。举个例子:
012与123可以组合,因为012剔除首位后与123剔除末位后都是12012与213则不能组合
- 代码主要逻辑:
- 先找出所有可能的密码串
pwds - 将所有密码串按前缀分组存入哈希表
pwdMap,前缀指的是剔除密码末位字符之后的字符串 - 使用回溯组合所有密码串
- 所有密码串都用完则顺序拼接并返回
- 先找出所有可能的密码串
3、代码
function crackSafe(n: number, k: number): string {
if (k === 1) return '0'.padStart(n, '0');
// 生成pwds
const pwds = new Array(k ** n).fill('0').map((v, i) => {
return k < 2 ? v : i.toString(k).padStart(n, "0");
});
if (n === 1) return pwds.join('');
const pwdMap = new Map();
for (const p of pwds) {
const prefix = p.slice(0, n - 1);
if (!pwdMap.has(prefix)) pwdMap.set(prefix, []);
pwdMap.get(prefix).push(p);
}
// 组合pwds
let ans = '';
function dfs(cur: string, used: Set<string>) {
if (ans) return;
if (used.size === pwds.length) {
const nums = [...used];
ans = nums[0] + nums.slice(1).map(w => w.slice(-1)).join('');
return;
}
const nums = pwdMap.get(cur.slice(1));
for (const n of nums) {
if (used.has(n)) continue;
used.add(n);
dfs(n, used);
used.delete(n);
}
}
for (const p of pwds) dfs(p, new Set([p]));
return ans;
}
4、复杂度
- 时间复杂度:
- 空间复杂度:
5、执行结果

6、思路2
这是思路1的优化版,时间复杂度更低。主要是猜测以任意密码开头都有可能找到最短字符串,因此枚举时只取第一个密码作为起点构造字符串。代码的不同表现在倒数第二行,移除了遍历,只取第一个密码 pwds[0] 进行回溯。
7、代码
function crackSafe(n: number, k: number): string {
if (k === 1) return '0'.padStart(n, '0');
// 生成pwds
const pwds = new Array(k ** n).fill('0').map((v, i) => {
return k < 2 ? v : i.toString(k).padStart(n, "0");
});
if (n === 1) return pwds.join('');
const pwdMap = new Map();
for (const p of pwds) {
const prefix = p.slice(0, n - 1);
if (!pwdMap.has(prefix)) pwdMap.set(prefix, []);
pwdMap.get(prefix).push(p);
}
// 组合pwds
let ans = '';
function dfs(cur: string, used: Set<string>) {
if (ans) return;
if (used.size === pwds.length) {
const nums = [...used];
ans = nums[0] + nums.slice(1).map(w => w.slice(-1)).join('');
return;
}
const nums = pwdMap.get(cur.slice(1));
for (const n of nums) {
if (used.has(n)) continue;
used.add(n);
dfs(n, used);
used.delete(n);
}
}
dfs(pwds[0], new Set([pwds[0]]));
return ans;
}
8、复杂度
- 时间复杂度:
- 空间复杂度:
9、执行结果
