27岁前端,技术都聊好了,offer都快发了,结果卡在一句话:只接受朝九晚六,不加班,周末双休,临时支援也不去。
![]()
我看这事挺真实的。27岁了,还把作息摆在前面,说明人家八成是被前几家公司狠狠干老实了。年轻时候还能靠咖啡和外卖硬扛,扛到这岁数,谁还没点职业病预备役。公司那边也别装无辜,嘴上说弹性,翻译过来常常就是“随时待命”,HR看完这条件,估计心里已经在默默切下一个候选人了。
说白了,这不是谁对谁错,就是两边都摊牌了。一个想好好活,一个想找个能随时顶上的。话难听点,能在面试桌上把这事讲明白,反倒比入职后三个月互相翻脸强。
一上来就暴力试密码,基本都会超时。
这种题最烦的地方,不是“密码有多少种”,而是你会反复试到很多重复后缀。比如保险箱密码长度是 n ,每一位取值范围是 0 ~ k-1 ,你每次输入一串,真正有意义的,其实只是最后 n 位。前面那一长串,很多时候只是为了把状态推到下一个可用后缀。
这题我第一眼就不太信普通回溯。因为它不是在找一个“可行解”,而是在找一条 尽量短、但能覆盖所有 n 位密码组合 的串。看到“覆盖所有组合一次”,味道就出来了,这基本就是 De Bruijn 序列 。
换个更接地气的理解:
把每个长度为 n-1 的串看成一个节点。 往后补一位数字,就得到一条边,这条边对应一个长度为 n 的密码。 题目要的,就是走一遍所有边,而且尽量不重复路。这个思路一旦立住,问题就从“试密码”变成“找欧拉路径”。
比如 n=2,k=2 :
节点:
0,1边:
00,01,10,11
只要把这四条边都走到,最后能得到 00110 ,里面连续长度为 2 的子串刚好把四种情况全覆盖了。
代码不用写得太花,核心就是 DFS 走边,边访问过就别再走,回溯时把当前补的数字记下来:
import java.util.HashSet;
import java.util.Set;
publicclassCrackSafe{
private Set seen = new HashSet<>;
private StringBuilder ans = new StringBuilder;
privateint k;
privateint n;
public String crackSafe(int n, int k){
this.n = n;
this.k = k;
if (n == 1 && k == 1) {
return"0";
}
String start = "0".repeat(n - 1);
dfs(start);
ans.append(start);
return ans.toString;
}
privatevoiddfs(String node){
for (int x = 0; x < k; x++) {
String edge = node + x;
if (seen.contains(edge)) {
continue;
}
seen.add(edge);
dfs(edge.substring(1));
ans.append(x);
}
}publicstaticvoidmain(String[] args){
CrackSafe solver = new CrackSafe;
System.out.println(solver.crackSafe(2, 2));
}
}
这里有两个地方很容易写别扭。
第一个, seen 记的是边,不是点。因为题目要求覆盖的是每一种长度为 n 的密码组合,不是每个 n-1 状态。
第二个, ans.append(x) 放在递归后面,不是前面。这个顺序其实就是在构造欧拉回路的结果,前序加会乱,后序加才对。
时间复杂度也别想复杂了,边一共就 k^n 条,每条边只走一次,所以是 O(k^n) 。这已经是这题该有的级别了,再低不现实,因为你至少得把所有密码组合碰一遍。
这题表面叫“破解保险箱”,实际上考的不是搜索有多猛,而是你能不能把“试密码”翻译成“走图”。一旦把状态看成后缀,把密码看成边,这题就顺了。否则很容易在回溯里转半天,代码写得热闹,结果全是在重复撞门。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.