跳转至

题目:997. 找到小镇的法官

给你 n 个人的社交关系(你知道任意两个人之间是否认识),然后请你找出这些人中的「名人」。 所谓「名人」有两个条件: 1、所有其他人都认识「名人」。 2、「名人」不认识任何其他人。 用图的说法:这个节点没有一条指向其他节点的有向边;且其他所有节点都有一条指向这个节点的有向边

核心

// 可以直接调用,能够返回 i 是否认识 j
bool knows(int i, int j);

// 请你实现:返回「名人」的编号
int findCelebrity(int n) {
    // todo
}

一、暴力解法

cand 是候选人(candidate) 的缩写,我们的暴力算法就是从头开始穷举,把每个人都视为候选人,判断是否符合「名人」的条件。

class Solution {
public:
    int findCelebrity(int n) {
        for (int cand = 0; cand < n; cand++) {
            int other;
            for (other = 0; other < n; other++) {
                if (cand == other) continue;
                // 保证其他人都认识 cand,且 cand 不认识任何其他人
                // 否则 cand 就不可能是名人
                if (knows(cand, other) || !knows(other, cand)) {
                    break;
                }
            }
            if (other == n) {
                // 找到名人
                return cand;
            }
        }
        // 没有一个人符合名人特性
        return -1;
    }
};

二、优化解法

1. 排除候选人,Time-- O(N)

只要观察任意两个候选人的关系,我一定能确定其中的一个人不是名人,把他排除。 不妨认为这两个人的编号分别是 cand 和 other,然后我们逐一分析每种情况,看看怎么排除掉一个人。 对于情况一,cand 认识 other,所以 cand 肯定不是名人,排除。因为名人不可能认识别人。 对于情况二,other 认识 cand,所以 other 肯定不是名人,排除。 对于情况三,他俩互相认识,肯定都不是名人,可以随便排除一个。 对于情况四,他俩互不认识,肯定都不是名人,可以随便排除一个。因为名人应该被所有其他人认识。

因此

if (knows(cand, other) || !knows(other, cand)) {
    // cand 不可能是名人
}
else {
    // other 不可能是名人
}

我们可以不断从候选人中选两个出来,然后排除掉一个,直到最后只剩下一个候选人,这时候再使用一个 for 循环判断这个候选人是否是货真价实的「名人」

算法思路:将所有候选人装进queue里,每次弹出cand和other,排除掉一个再拿回来

2.空间复杂度优化O(N)变O(1)

三、另类/更优/更简洁解法:入度、出度

已知总人数为n,名人的入度为0,出度为n-1。 遍历一遍关系算出每个人的出度、入度,over