把OpenClaw往全公司一铺,味儿一下就不对了。鼠标停几秒、切了几次窗口、几点开机、几点锁屏,后台全给你记着,活像上班先坐牢,工位自带电子狱警。
最逗的是,老板总觉得数据一上墙,员工就会突然爱岗敬业。结果呢,大概率是大家先学会怎么“演工作”:手不敢停,窗口不敢切,连发呆都得装成在思考需求。表面更忙了,活未必更好。
说到底,真能干活的人,靠的是目标清楚、流程别乱、破会少开,不是盯着人家鼠标几点几分挪了两下。监控开这么细,公司安全感是有了,信任感基本也顺手清空了。
算法题:账户合并
这题要是你真拿 DFS 硬扫,样例一过,数据一大基本就开始烦了。
麻烦不在名字,在邮箱。两个账户名都叫 John,未必是一个人;两个账户名不一样,只要邮箱串上了,反而得合并。这个判断点很多人第一眼就写歪,先拿 name 当 key,后面怎么补都别扭。账户合并这题,真正该连的是邮箱,不是人名。
我一般先把它看成一张图:同一个账户里的邮箱,两两可达。那最省事的做法不是现建图现搜,而是直接上并查集,把同账户下的邮箱挂到一个根上。后面按根分组、排序、再把名字补回去,事情就干净了。这个题代码不长,但有两个地方容易写脏:一个是邮箱到姓名的映射别丢,另一个是每个账户里第一个邮箱最好当锚点,后面的邮箱都往它上面 union,别自己给自己造复杂度。整体写法参考这种手感就够了,风格上也尽量贴近实战排查式表达。
先看并查集核心:
class UnionFind {
privatefinal Map<String, String> parent = new HashMap<>();
public void add(String x) {
parent.putIfAbsent(x, x);
}
public String find(String x) {
String p = parent.get(x);
if (!p.equals(x)) {
parent.put(x, find(p));
}
return parent.get(x);
}
public void union(String a, String b) {
String pa = find(a);
String pb = find(b);
if (!pa.equals(pb)) {
parent.put(pa, pb);
}
}
}
主逻辑也别绕:
public List<List<String>> accountsMerge(List<List<String>> accounts) {
UnionFind uf = new UnionFind();
Map<String, String> emailToName = new HashMap<>();
for (List<String> acc : accounts) {
String name = acc.get(0);
String first = acc.get(1);
uf.add(first);
emailToName.put(first, name);
for (int i = 2; i < acc.size(); i++) {
String mail = acc.get(i);
uf.add(mail);
emailToName.put(mail, name);
uf.union(first, mail);
}
}
Map<String, List<String>> group = new HashMap<>();
for (String mail : emailToName.keySet()) {
String root = uf.find(mail);
group.computeIfAbsent(root, k -> new ArrayList<>()).add(mail);
}
List<List<String>> ans = new ArrayList<>();
for (List<String> mails : group.values()) {
Collections.sort(mails);
List<String> row = new ArrayList<>();
row.add(emailToName.get(mails.get(0)));
row.addAll(mails);
ans.add(row);
}
return ans;
}
复杂度也比较顺:设邮箱总数为 n,排序前的并查集合并和查找近似看成 O(n),真正花时间的是每个分组内部排序,总体是 O(n log n)。
这题说白了不考花活,考你有没有抓住“合并关系到底落在哪个字段上”。抓对了,就是并查集模板题。抓错了,后面全是补丁。