搜寻名人

Submission

运行时间: 664 ms

内存: 16.4 MB

# The knows API is already defined for you.
# return a bool, whether a knows b
# def knows(a: int, b: int) -> bool:


class Solution:
    def __init__(self):
        self.cache = {}

    def cache_knows(self, i, j):
        """
        We can use a local variable to cache the result of knows API since its network latency may be high
        """
        if (i, j) in self.cache:
            return self.cache[(i, j)]
        else:
            self.cache[(i, j)] = knows(i, j)
            return self.cache[(i, j)]

    def is_celebrity(self, candidate, n):
        for i in range(n):
            if i == candidate:
                continue
            if not (self.cache_knows(i, candidate) and not self.cache_knows(candidate, i)):
                return False
        return True

    def findCelebrity(self, n: int) -> int:

        candidate = 0
        # loop invariant: (candidate, i) does not contain the celebrity which can be approved by mathematical induction
        for i in range(1, n):
            # knows(candidate, i) == 0 -> i is not the celebrity
            # knows(candidate, i) == 1 -> candidate is not the celebrity
            if self.cache_knows(candidate, i) == 1:
                candidate = i

        if self.is_celebrity(candidate, n):
            return candidate
        return -1

Explain

这个题解使用了两次遍历来找到名人。第一次遍历从候选人0开始,将候选人与之后的每个人进行比较,如果候选人认识当前人,说明候选人不是名人,将当前人设为新的候选人。这样第一次遍历后,我们就能找出潜在的名人候选人。第二次遍历用来验证这个候选人是否真的是名人,即所有其他人都认识候选人,且候选人不认识任何其他人。同时使用了缓存来存储已经调用过的knows API结果,避免重复调用。

时间复杂度: O(n)

空间复杂度: O(n^2)

# The knows API is already defined for you.
# return a bool, whether a knows b
# def knows(a: int, b: int) -> bool:


class Solution:
    def __init__(self):
        self.cache = {}

    def cache_knows(self, i, j):
        """
        We can use a local variable to cache the result of knows API since its network latency may be high
        """
        if (i, j) in self.cache:
            return self.cache[(i, j)]
        else:
            self.cache[(i, j)] = knows(i, j)
            return self.cache[(i, j)]

    def is_celebrity(self, candidate, n):
        for i in range(n):
            if i == candidate:
                continue
            # candidate必须不认识i,且i必须认识candidate,否则candidate就不是名人
            if not (self.cache_knows(i, candidate) and not self.cache_knows(candidate, i)):
                return False
        return True

    def findCelebrity(self, n: int) -> int:
        candidate = 0
        # 遍历寻找可能的名人候选人
        # 循环不变式:区间(candidate, i)内不包含名人,可以用数学归纳法证明
        for i in range(1, n):
            # knows(candidate, i) == 0 -> i 不是名人
            # knows(candidate, i) == 1 -> candidate 不是名人
            if self.cache_knows(candidate, i) == 1:
                candidate = i
        
        # 再次遍历,验证候选人是否真的是名人
        if self.is_celebrity(candidate, n):
            return candidate
        return -1

Explore

在第一次遍历中,如果`candidate`认识`i`,则根据名人的定义(名人不认识任何其他人,但所有其他人都认识名人),`candidate`显然不能是名人。因此,我们需要更换候选人。选择`i`作为新的候选人是基于假设如果`candidate`不是名人,而`candidate`认识`i`,那么`i`有可能是名人。这种方法不会错过名人,因为每次更换候选人都是因为当前候选人违反了名人的条件。最终,这个过程将筛选出一个唯一的可能的名人候选人,然后需要通过第二次遍历来验证这个候选人是否真的是名人。

如果`cache`中没有存储`knows`函数的某次调用结果,那么`is_celebrity`函数将直接调用`knows`函数来获取结果,并将其存储在`cache`中。这将确保每次`knows`调用的结果都被缓存,减少了重复调用的需要。如果没有使用缓存,每次需要验证候选人与其他所有人的关系时都可能需要重新调用`knows`,这会增加API调用次数,导致性能下降。因此,使用缓存可以显著优化性能,尤其是在网络延迟或API调用成本较高的情况下。

缓存`knows`API的结果特别有效于减少网络或API调用延迟,以及调用次数较多的情况。例如,在高并发环境或网络延迟高的情况下,缓存可以显著提升性能。然而,如果缓存过多数据,的确有可能影响性能,尤其是在内存有限的系统中。缓存占用过多内存可能导致系统其他部分的性能下降,或者触发更频繁的垃圾收集,从而影响整体性能。因此,合理设计缓存的大小和清理策略是很重要的,以确保缓存机制能够提供最大的性能优势而不引入额外的性能负担。