Infra SWE / App SWE · VO Coding

LinkedIn 找 Celebrity:先排除候选,再完整验证

n−1 次比较选唯一可能人选,再验证‘不认识任何人、所有人都认识他’,避免只做排除不做证实。

题目重点:n−1 次比较选唯一可能人选,再验证‘不认识任何人、所有人都认识他’,避免只做排除不做证实。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练版 n 人编号 0..n-1,`knows(a,b)` 返回 a 是否认识 b;Celebrity c 不认识任何其他人且每个其他人都认识 c。n=0 返回 -1。
  • 是否认识自己不影响判定;题目只给题名/原题描述,若真实题目有变体(噪声关系、查询上限),需另核。

解题路线

  1. 候选从 0 开始与每个人 i 比:若 candidate 认识 i,candidate 不可能是 Celebrity,换 i;否则 i 不可能是 Celebrity,保持 candidate。每次比较淘汰一人,n−1 次后至多一个候选。
  2. 排除阶段没有证明剩余者真的符合全部关系,必须第二趟验证对每个 i≠c:knows(c,i) 为 false 且 knows(i,c) 为 true。任何一项不符返回 -1。
  3. 正确性来自每轮在两人中至少排除一人:若 c→i,则 c 出局;否则 i 出局。一个 Celebrity 若存在,不会在这条规则下被排除。
  4. 若 knows 是昂贵网络 API,可缓存重复关系,统计最多约 3n 次逻辑调用;训练代码不假设 API 可批量。

手动走一遍

三人 A、B、C:A 认识 B,则 A 出局;B 不认识 C,则 C 出局,B 为候选。还需验证 B 不认识 A/C、A/C 都认识 B;若其中一项失败,答案不存在。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

def find_celebrity(n: int, knows) -> int:
    if n <= 0:
        return -1
    candidate = 0
    for person in range(1, n):
        if knows(candidate, person):
            candidate = person
    for person in range(n):
        if person != candidate and (knows(candidate, person) or not knows(person, candidate)):
            return -1
    return candidate

复杂度

O(N) 次 knows 调用、O(1) 额外空间;验证阶段最多 2(N−1) 次调用,排除阶段 N−1。训练代码 20,000 组随机有向关系矩阵与全枚举 oracle 对照。

易错点与追问

容易失分的边界

  • 只完成淘汰阶段就返回 candidate。
  • 关系方向写反。
  • 把互相认识当 Celebrity 条件。
  • n=0 未处理。
  • 把两人的相同名字/属性当节点身份。

继续深挖

  • knows API 很慢时如何批量/缓存?
  • 有少量错误关系时如何找‘近似 Celebrity’?
  • 如果只需判断候选是否存在,能否少于线性查询?
  • 关系随查询变化时还可证明吗?
✓已复制微信号!