← Назад к вопросам

Поиск знаменитости

2.0 Middle🔥 121 комментариев
#Python Core

Условие

В группе из K человек нужно определить знаменитость.

Знаменитость — это человек, который никого не знает, но все знают его.

Дана функция knows(a, b), которая возвращает True, если a знает b.

Минимизируйте количество вызовов функции knows().

Пример

Если в группе 3 человека и person 2 — знаменитость:

  • knows(0, 2) → True
  • knows(1, 2) → True
  • knows(2, 0) → False
  • knows(2, 1) → False

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Поиск знаменитости в группе людей

Это классическая задача оптимизации, требующая минимизации вызовов функции knows(). Рассмотрим несколько подходов с разными временными и пространственными сложностями.

1. Наивный подход — O(n²) вызовов

def find_celebrity_naive(n: int, knows) -> int:
    """Наивный подход: проверяем каждого человека полностью."""
    for i in range(n):
        is_celebrity = True
        
        # Проверяем, что i никого не знает
        for j in range(n):
            if i != j and knows(i, j):
                is_celebrity = False
                break
        
        if not is_celebrity:
            continue
        
        # Проверяем, что все знают i
        for j in range(n):
            if i != j and not knows(j, i):
                is_celebrity = False
                break
        
        if is_celebrity:
            return i
    
    return -1  # Знаменитости нет

Анализ: Количество вызовов: O(n²) в худшем случае, Пространство: O(1), Проблема: очень неэффективно для больших n

2. Оптимальный подход — O(n) вызовов с двумя указателями

Это лучшее решение — минимизирует количество вызовов функции.

def find_celebrity_optimal(n: int, knows) -> int:
    """Оптимальный подход: O(n) вызовов knows()."""
    
    # Этап 1: Исключение кандидатов (O(n) вызовов)
    left, right = 0, n - 1
    
    while left < right:
        if knows(left, right):
            left += 1
        else:
            right -= 1
    
    candidate = left
    
    # Этап 2: Проверка кандидата (O(n) вызовов)
    for i in range(n):
        if i != candidate and knows(candidate, i):
            return -1
    
    for i in range(n):
        if i != candidate and not knows(i, candidate):
            return -1
    
    return candidate

Почему это работает? Если knows(a, b) == True: a знает b, значит a не может быть знаменитостью. Если knows(a, b) == False: b не может быть знаменитостью. На каждом шаге исключаем ровно одного человека за один вызов!

Анализ сложности: Этап 1: O(n-1) вызовов, Этап 2: O(n) вызовов, Всего: O(n) вызовов, Пространство: O(1)

3. Пример работы алгоритма

def example_knows(a: int, b: int) -> bool:
    """Знаменитость — person 2."""
    return (a, b) in [(0, 2), (1, 2)]

result = find_celebrity_optimal(3, example_knows)
print(f"Знаменитость: {result}")  # Выведет: 2

4. Улучшенная версия с обработкой ошибок

from typing import Callable, Optional

def find_celebrity(
    n: int,
    knows: Callable[[int, int], bool],
    use_cache: bool = True
) -> Optional[int]:
    """Поиск знаменитости с опциональным кэшированием."""
    
    if n <= 1:
        return 0 if n == 1 else -1
    
    cache = {} if use_cache else None
    
    def knows_cached(a: int, b: int) -> bool:
        if cache is not None:
            key = (a, b)
            if key not in cache:
                cache[key] = knows(a, b)
            return cache[key]
        return knows(a, b)
    
    left, right = 0, n - 1
    
    while left < right:
        if knows_cached(left, right):
            left += 1
        else:
            right -= 1
    
    candidate = left
    
    for i in range(n):
        if i != candidate:
            if knows_cached(candidate, i):
                return -1
            if not knows_cached(i, candidate):
                return -1
    
    return candidate

5. Граничные случаи и тестирование

import unittest
from typing import Dict, Callable

class TestFindCelebrity(unittest.TestCase):
    
    def _create_knows_func(self, graph: Dict[int, set]) -> Callable:
        def knows(a: int, b: int) -> bool:
            return b in graph.get(a, set())
        return knows
    
    def test_single_person(self):
        knows = self._create_knows_func({})
        result = find_celebrity(1, knows)
        self.assertEqual(result, 0)
    
    def test_no_celebrity(self):
        graph = {0: {1}, 1: {0}}
        knows = self._create_knows_func(graph)
        result = find_celebrity(2, knows)
        self.assertEqual(result, -1)
    
    def test_celebrity_exists(self):
        graph = {0: {2}, 1: {2}, 2: set()}
        knows = self._create_knows_func(graph)
        result = find_celebrity(3, knows)
        self.assertEqual(result, 2)

Вывод

Оптимальное решение использует метод двух указателей: минимально O(n) вызовов функции, O(1) памяти, ясная логика исключения кандидатов. Это решение часто задают на собеседованиях в крупных технологических компаниях.