🧠 Что выведет код
Варианты ответа:
A)
B)
C)
D)
✅ Правильный ответ:B
Разбор
Интуитивно кажется, что каждая лямбда «запоминает» своё значение
Все три лямбды в списке
Это называется late binding — тело функции обращается к переменной по имени в момент вызова, а не в момент определения.
Как исправить, если нужно зафиксировать значение на каждой итерации:
Аргумент по умолчанию вычисляется один раз, в момент определения лямбды — поэтому такой трюк «замораживает» текущее значение
Вывод
Замыкания в Python захватывают переменные, а не их значения. Это всплывает везде, где создаются функции внутри циклов — от лямбд до обработчиков событий в GUI и async-калбэков. Если интервьюер спрашивает «что выведет этот код» с циклом и лямбдой — это почти всегда проверка именно на понимание late binding.
🐍Вопросы с собесов -> ProstoPython
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])
Варианты ответа:
A)
[0, 1, 2]B)
[2, 2, 2]C)
[0, 0, 0]D)
RuntimeError✅ Правильный ответ:
Разбор
Интуитивно кажется, что каждая лямбда «запоминает» своё значение
i в момент создания. Но это не так — лямбда не копирует значение, она захватывает саму переменную i по ссылке на её область видимости.Все три лямбды в списке
funcs смотрят на одну и ту же ячейку памяти — переменную i из окружающей функции (замыкание, closure). Цикл for не создаёт новую переменную на каждой итерации, он просто переиспользует одну и ту же i и меняет её значение. К моменту, когда мы реально вызываем f() в списковом включении, цикл уже завершён, и i равна последнему значению — 2.Это называется late binding — тело функции обращается к переменной по имени в момент вызова, а не в момент определения.
Как исправить, если нужно зафиксировать значение на каждой итерации:
funcs = []
for i in range(3):
funcs.append(lambda i=i: i) # значение по умолчанию фиксируется сразу
print([f() for f in funcs]) # [0, 1, 2]
Аргумент по умолчанию вычисляется один раз, в момент определения лямбды — поэтому такой трюк «замораживает» текущее значение
i.Вывод
Замыкания в Python захватывают переменные, а не их значения. Это всплывает везде, где создаются функции внутри циклов — от лямбд до обработчиков событий в GUI и async-калбэков. Если интервьюер спрашивает «что выведет этот код» с циклом и лямбдой — это почти всегда проверка именно на понимание late binding.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3
📈 От O(n²) до O(n)
Задача: дан список чисел, нужно проверить, есть ли в нём два элемента, сумма которых равна
Вопрос настолько классический, что его знают почти все — но многие всё равно скатываются в перебор, потому что «так проще написать».
❌ Наивное решение
Сложность: O(n²) — для каждого элемента перебираем все последующие.
✅ Оптимизированное решение
Сложность: O(n) — один проход, поиск в множестве.
💡 Что изменилось
Ключевая идея — развернуть вопрос. Вместо «есть ли пара, дающая в сумме target» спрашиваем на каждом шаге: «я уже видел число, которое в паре с текущим даст target?». Это
Проверка
Обрати внимание на порядок действий: сначала проверяем
⚡️ Вывод
Если в задаче фигурирует «найти пару/подмножество с определённой суммой» — это почти всегда сигнал заменить вложенный цикл на set или dict. Ищи не «как перебрать все пары», а «что нужно было увидеть раньше, чтобы текущий элемент завершил пару».
🐍Вопросы с собесов -> ProstoPython
Задача: дан список чисел, нужно проверить, есть ли в нём два элемента, сумма которых равна
target.Вопрос настолько классический, что его знают почти все — но многие всё равно скатываются в перебор, потому что «так проще написать».
❌ Наивное решение
def has_pair_with_sum(nums: list[int], target: int) -> bool:
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return True
return False
Сложность: O(n²) — для каждого элемента перебираем все последующие.
✅ Оптимизированное решение
def has_pair_with_sum(nums: list[int], target: int) -> bool:
seen = set()
for num in nums:
if target - num in seen:
return True
seen.add(num)
return False
Сложность: O(n) — один проход, поиск в множестве.
💡 Что изменилось
Ключевая идея — развернуть вопрос. Вместо «есть ли пара, дающая в сумме target» спрашиваем на каждом шаге: «я уже видел число, которое в паре с текущим даст target?». Это
target - num.Проверка
in set работает в среднем за O(1) благодаря хеш-таблице под капотом, а не за O(n), как поиск в списке. Именно поэтому переход с list на set меняет асимптотику всей задачи, а не просто ускоряет константу.Обрати внимание на порядок действий: сначала проверяем
target - num in seen, потом добавляем num. Если поменять местами, для случая target == 2 * num алгоритм ошибочно сочтёт число парой самому себе.⚡️ Вывод
Если в задаче фигурирует «найти пару/подмножество с определённой суммой» — это почти всегда сигнал заменить вложенный цикл на set или dict. Ищи не «как перебрать все пары», а «что нужно было увидеть раньше, чтобы текущий элемент завершил пару».
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍2
Ребят, запустил Telegram-канал с вакансиями для Python-разработчиков 🐍
Сейчас канал только начинает расти, поэтому хочу собрать первую аудиторию и постепенно привлечь больше работодателей.
Если вы Python-разработчик, подписывайтесь, чтобы не пропустить новые вакансии.
А если вы HR или занимаетесь наймом Python-разработчиков, просто возьмите канал на заметку. Возможно, скоро он вам пригодится 👀
t.me/python_work1
Сейчас канал только начинает расти, поэтому хочу собрать первую аудиторию и постепенно привлечь больше работодателей.
Если вы Python-разработчик, подписывайтесь, чтобы не пропустить новые вакансии.
А если вы HR или занимаетесь наймом Python-разработчиков, просто возьмите канал на заметку. Возможно, скоро он вам пригодится 👀
t.me/python_work1
🔥2
🧰 Читаемые условия: all()/any() вместо ручных флагов
Проверка «выполняется ли условие для всех/хотя бы одного элемента» — то место, где Python-код часто выдаёт бэкграунд разработчика.
❌ Плохой код
✅ Улучшенный код
💥 Краткое объяснение
Первый вариант — это ручная реализация того, что уже есть в стандартной библиотеке, причём реализация не бесплатная: заводится промежуточный флаг, состояние которого нужно отслеживать, плюс явный
Та же логика работает в обратную сторону:
🐍Вопросы с собесов -> ProstoPython
Проверка «выполняется ли условие для всех/хотя бы одного элемента» — то место, где Python-код часто выдаёт бэкграунд разработчика.
❌ Плохой код
def all_users_verified(users: list[dict]) -> bool:
is_all_verified = True
for user in users:
if not user["is_verified"]:
is_all_verified = False
break
return is_all_verified
✅ Улучшенный код
def all_users_verified(users: list[dict]) -> bool:
return all(user["is_verified"] for user in users)
💥 Краткое объяснение
Первый вариант — это ручная реализация того, что уже есть в стандартной библиотеке, причём реализация не бесплатная: заводится промежуточный флаг, состояние которого нужно отслеживать, плюс явный
break. Всё это — шум, который отвлекает от самой сути проверки.all() принимает любой итерируемый объект и возвращает True, только если каждый элемент истинный. Важный нюанс: генератор (user["is_verified"] for user in users) вычисляется лениво — как только all() натыкается на первый False, он немедленно останавливается и не проходит по остальным элементам. То есть по производительности это тот же short-circuit, что и break в ручном варианте, только без явного управления состоянием.Та же логика работает в обратную сторону:
any() возвращает True, если истинен хотя бы один элемент, и тоже останавливается на первом совпадении.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥1👌1
🧠 Interview Thinking: найти первый неповторяющийся символ
Задача: дана строка, нужно найти первый символ, который встречается в ней ровно один раз. Если такого нет — вернуть
👶 Как думает junior
Для каждого символа проверить, сколько раз он встречается в строке — и как только нашли символ с count == 1, вернуть его.
Код рабочий и на маленьких строках даже быстрый. Проблема всплывает, если спросить про сложность:
🧠 Как думает сильный кандидат
Сначала разделить задачу на две части: «посчитать частоту каждого символа» и «найти первый по порядку с частотой 1». Первую часть можно сделать за один проход, вторую — тоже за один, итого O(n) вместо O(n²).
Здесь два прохода по строке — O(2n), что асимптотически всё равно O(n). Но важно не просто написать
🎯 Что хочет интервьюер
Не код с
Хороший вопрос, который стоит задать в ответ: «а какого размера ожидается строка на входе» — если это заведомо короткие строки (пароли, коды), O(n²) может быть абсолютно приемлемым, и оверинжиниринг с
💡 Вывод
Разница между junior и сильным кандидатом здесь не в знании
🐍Вопросы с собесов -> ProstoPython
Задача: дана строка, нужно найти первый символ, который встречается в ней ровно один раз. Если такого нет — вернуть
None.find_first_unique("swiss") # "w"👶 Как думает junior
Для каждого символа проверить, сколько раз он встречается в строке — и как только нашли символ с count == 1, вернуть его.
def find_first_unique(s: str) -> str | None:
for char in s:
if s.count(char) == 1:
return char
return None
Код рабочий и на маленьких строках даже быстрый. Проблема всплывает, если спросить про сложность:
s.count(char) сам по себе O(n), и он вызывается для каждого символа — то есть в худшем случае O(n²). На строке в миллион символов это уже секунды вместо миллисекунд.🧠 Как думает сильный кандидат
Сначала разделить задачу на две части: «посчитать частоту каждого символа» и «найти первый по порядку с частотой 1». Первую часть можно сделать за один проход, вторую — тоже за один, итого O(n) вместо O(n²).
from collections import Counter
def find_first_unique(s: str) -> str | None:
counts = Counter(s)
for char in s:
if counts[char] == 1:
return char
return None
Здесь два прохода по строке — O(2n), что асимптотически всё равно O(n). Но важно не просто написать
Counter, а объяснить, зачем нужен именно второй проход по s, а не по counts: Counter в Python 3.7+ сохраняет порядок вставки, но нам нужен порядок именно исходной строки, а не порядок первого появления в словаре — хотя на практике они совпадают, полагаться на это как на гарантию не стоит без проверки документации под конкретную версию.🎯 Что хочет интервьюер
Не код с
Counter сам по себе — это можно найти в любом решении на LeetCode. Интервьюер хочет услышать, что кандидат сам заметил скрытую стоимость s.count() внутри цикла, назвал её вслух и предложил trade-off: чуть больше памяти (хеш-таблица на все уникальные символы) в обмен на линейное время вместо квадратичного.Хороший вопрос, который стоит задать в ответ: «а какого размера ожидается строка на входе» — если это заведомо короткие строки (пароли, коды), O(n²) может быть абсолютно приемлемым, и оверинжиниринг с
Counter не всегда плюс.💡 Вывод
Разница между junior и сильным кандидатом здесь не в знании
Counter, а в привычке проверять код на скрытые вложенные проходы — count(), in list, index() внутри цикла почти всегда прячут лишнюю степень сложности.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥2
❌ Rookie Mistakes
🤔 Что ожидает новичок
Ожидаемый вывод:
💥 Что происходит на самом деле
Мы поменяли
🧠 Почему
Проверить это легко:
Внешние контейнеры — разные, внутренние — общие.
✅ Исправление
Для настоящей независимой копии вложенной структуры нужен deep copy:
Для простого случая с числами внутри можно обойтись и без импорта
⚡️ Вывод
🐍Вопросы с собесов -> ProstoPython
matrix = [[0, 0, 0] for _ in range(3)]
board = matrix.copy()
board[0][0] = 1
print(matrix[0])
🤔 Что ожидает новичок
board = matrix.copy() создаёт независимую копию — значит, изменение board никак не должно затронуть matrix.Ожидаемый вывод:
[0, 0, 0]
💥 Что происходит на самом деле
[1, 0, 0]
Мы поменяли
board, а изменился и matrix, хотя вроде бы сделали копию.🧠 Почему
.copy() (как и срез matrix[:], и list(matrix)) делает shallow copy — поверхностную копию. Это значит, что копируется только внешний список, а вот его элементы копируются не по значению, а по ссылке.matrix — это список списков. Внешний .copy() создаёт новый внешний контейнер, но каждый элемент внутри — это всё та же ссылка на тот же вложенный список, что и в оригинале. board[0] и matrix[0] — это буквально один и тот же объект в памяти, просто до него можно добраться двумя путями.Проверить это легко:
print(board[0] is matrix[0]) # True
print(board is matrix) # False
Внешние контейнеры — разные, внутренние — общие.
✅ Исправление
Для настоящей независимой копии вложенной структуры нужен deep copy:
import copy
board = copy.deepcopy(matrix)
board[0][0] = 1
print(matrix[0]) # [0, 0, 0] — не изменился
Для простого случая с числами внутри можно обойтись и без импорта
copy, пересобрав вложенные списки вручную:board = [row.copy() for row in matrix]
⚡️ Вывод
.copy() копирует только один уровень вложенности. Если внутри структуры есть изменяемые объекты (списки, словари) — они останутся общими между оригиналом и копией. Для многомерных структур default should be copy.deepcopy(), а не .copy().🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍2
📈 От O(n²) до O(n)
Задача: даны два списка чисел, нужно проверить, есть ли между ними хотя бы один общий элемент.
❌ Наивное решение
Для каждого элемента первого списка проверяем, есть ли он во втором.
Сложность: O(n·m) — вложенный цикл, где для каждого элемента первого списка перебирается весь второй.
✅ Оптимизированное решение
Превращаем один из списков в множество и проверяем принадлежность.
Сложность: O(n + m) — построение множества линейно, проверка через
💡 Что изменилось
Здесь смена структуры данных меняет саму природу операции «проверить принадлежность». В списке она стоит O(n), потому что интерпретатор вынужден пройти по элементам один за другим. В множестве та же проверка — O(1) в среднем, потому что под капотом хеш-таблица сразу вычисляет, куда должен попасть элемент, без перебора.
Многие вместо
⚡️ Вывод
Как только видишь «есть ли пересечение / общий элемент между двумя коллекциями» — это сигнал заменить вложенный цикл на set. Отдельно стоит запомнить
🐍Вопросы с собесов -> ProstoPython
Задача: даны два списка чисел, нужно проверить, есть ли между ними хотя бы один общий элемент.
has_common(nums1=[1, 2, 3], nums2=[4, 5, 3]) # True
❌ Наивное решение
Для каждого элемента первого списка проверяем, есть ли он во втором.
def has_common(nums1: list[int], nums2: list[int]) -> bool:
for a in nums1:
for b in nums2:
if a == b:
return True
return False
Сложность: O(n·m) — вложенный цикл, где для каждого элемента первого списка перебирается весь второй.
✅ Оптимизированное решение
Превращаем один из списков в множество и проверяем принадлежность.
def has_common(nums1: list[int], nums2: list[int]) -> bool:
return not set(nums1).isdisjoint(nums2)
Сложность: O(n + m) — построение множества линейно, проверка через
isdisjoint тоже линейна.💡 Что изменилось
Здесь смена структуры данных меняет саму природу операции «проверить принадлежность». В списке она стоит O(n), потому что интерпретатор вынужден пройти по элементам один за другим. В множестве та же проверка — O(1) в среднем, потому что под капотом хеш-таблица сразу вычисляет, куда должен попасть элемент, без перебора.
Многие вместо
isdisjoint пишут set(nums1) & set(nums2) и проверяют результат на пустоту — это тоже правильно по сложности, но isdisjoint быстрее на практике, потому что может остановиться на первом же совпадении и не обязан строить пересечение целиком, если второй аргумент — любой итерируемый объект, а не только множество.⚡️ Вывод
Как только видишь «есть ли пересечение / общий элемент между двумя коллекциями» — это сигнал заменить вложенный цикл на set. Отдельно стоит запомнить
isdisjoint — он часто выразительнее и быстрее, чем ручная проверка пересечения через &.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🏆2
🧰 Code Cleanup: словарь вместо цепочки if/elif
Диспетчеризация по типу действия — то место, где длинная цепочка условий обычно сигнализирует о плохом дизайне.
❌ Плохой код
✅ Улучшенный код
💥 Краткое объяснение
Проблема цепочки
Словарь-диспетчер превращает набор веток в данные. Добавление нового типа события — это добавление одной строки в
Отдельный плюс —
⚡️ Одно правило
Если цепочка
🐍Вопросы с собесов -> ProstoPython
Диспетчеризация по типу действия — то место, где длинная цепочка условий обычно сигнализирует о плохом дизайне.
❌ Плохой код
def handle_event(event_type: str, payload: dict) -> str:
if event_type == "created":
return f"Created: {payload['id']}"
elif event_type == "updated":
return f"Updated: {payload['id']}"
elif event_type == "deleted":
return f"Deleted: {payload['id']}"
elif event_type == "archived":
return f"Archived: {payload['id']}"
else:
return f"Unknown event: {event_type}"
✅ Улучшенный код
HANDLERS = {
"created": lambda p: f"Created: {p['id']}",
"updated": lambda p: f"Updated: {p['id']}",
"deleted": lambda p: f"Deleted: {p['id']}",
"archived": lambda p: f"Archived: {p['id']}",
}
def handle_event(event_type: str, payload: dict) -> str:
handler = HANDLERS.get(event_type)
if handler is None:
return f"Unknown event: {event_type}"
return handler(payload)💥 Краткое объяснение
Проблема цепочки
if/elif не в производительности — на 4-5 условиях разницы почти нет. Проблема в том, что она плохо масштабируется: каждый новый тип события — это ещё одна ветка, вставленная куда-то в середину растущего блока, и с этим неудобно работать при code review, легко случайно сломать порядок проверок.Словарь-диспетчер превращает набор веток в данные. Добавление нового типа события — это добавление одной строки в
HANDLERS, а не правка ветвящейся логики. Поиск по ключу в словаре — O(1), так что при росте количества обработчиков (а не количества вызовов) производительность не деградирует, в отличие от if/elif, где в худшем случае приходится пройти все условия по очереди.Отдельный плюс —
HANDLERS можно тестировать и модифицировать независимо от функции handle_event, например регистрировать обработчики динамически из разных модулей.⚡️ Одно правило
Если цепочка
if/elif проверяет равенство одной и той же переменной разным константам — это структура "ключ → действие", и её почти всегда стоит превратить в словарь.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥2
📈 От O(n·k) до O(n)
Задача: дан список чисел и число k, нужно найти максимальную сумму среди всех подряд идущих подмассивов длины k.
❌ Наивное решение
Для каждой стартовой позиции суммируем k элементов заново.
Сложность: O(n·k) — для каждого из ~n стартовых окон заново пересчитываем сумму k элементов.
✅ Оптимизированное решение
Сложность: O(n) — сумма поддерживается инкрементально, каждый элемент обрабатывается один раз.
💡 Что изменилось
Ключевая идея — не пересчитывать то, что уже посчитано. Между соседними окнами
Это классический sliding window — паттерн, который стоит узнавать сразу, как только видишь "подмассив/подстрока фиксированной или переменной длины" в условии задачи. Здесь окно фиксированного размера, поэтому обновление тривиальное:
⚡️ Вывод
Если в задаче фигурирует "подряд идущие элементы" и надо посчитать что-то (сумму, среднее, количество уникальных) для каждого окна — думай sliding window, а не пересчёт с нуля на каждой позиции. Вопрос себе: что меняется между соседними окнами, и можно ли обновить результат инкрементально?
🐍Вопросы с собесов -> ProstoPython
Задача: дан список чисел и число k, нужно найти максимальную сумму среди всех подряд идущих подмассивов длины k.
max_subarray_sum([2, 1, 5, 1, 3, 2], k=3) # 9 (5+1+3)
❌ Наивное решение
Для каждой стартовой позиции суммируем k элементов заново.
def max_subarray_sum(nums: list[int], k: int) -> int:
best = float("-inf")
for i in range(len(nums) - k + 1):
window_sum = sum(nums[i:i + k])
best = max(best, window_sum)
return best
Сложность: O(n·k) — для каждого из ~n стартовых окон заново пересчитываем сумму k элементов.
✅ Оптимизированное решение
def max_subarray_sum(nums: list[int], k: int) -> int:
window_sum = sum(nums[:k])
best = window_sum
for i in range(k, len(nums)):
window_sum += nums[i] - nums[i - k]
best = max(best, window_sum)
return best
Сложность: O(n) — сумма поддерживается инкрементально, каждый элемент обрабатывается один раз.
💡 Что изменилось
Ключевая идея — не пересчитывать то, что уже посчитано. Между соседними окнами
[i, i+k) и [i+1, i+k+1) разница ровно в двух элементах: один уходит слева, другой добавляется справа. Наивное решение это игнорирует и каждый раз суммирует k элементов с нуля, хотя k-1 из них уже входили в предыдущую сумму.Это классический sliding window — паттерн, который стоит узнавать сразу, как только видишь "подмассив/подстрока фиксированной или переменной длины" в условии задачи. Здесь окно фиксированного размера, поэтому обновление тривиальное:
+nums[i] - nums[i-k]. Для окна переменного размера логика чуть сложнее (нужны два указателя, которые двигаются независимо), но идея та же — переиспользовать уже посчитанное состояние вместо пересчёта.⚡️ Вывод
Если в задаче фигурирует "подряд идущие элементы" и надо посчитать что-то (сумму, среднее, количество уникальных) для каждого окна — думай sliding window, а не пересчёт с нуля на каждой позиции. Вопрос себе: что меняется между соседними окнами, и можно ли обновить результат инкрементально?
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍2
🧠 Что выведет код
Варианты ответа:
A)
B)
C)
D)
✅ Правильный ответ:B
Разбор
Ключи в словаре сравниваются не по типу, а по
Для Python это три записи с одинаковым ключом. Каждая следующая просто перезаписывает значение по уже существующему хешу:
Важный нюанс: значение перезаписывается, а вот объект ключа — нет. CPython хранит тот ключ, который был вставлен первым. Поэтому в итоговом словаре ключ — это
Вывод
Если используешь
🐍Вопросы с собесов -> ProstoPython
d = {1: 'a', True: 'b', 1.0: 'c'}
print(d)Варианты ответа:
A)
{1: 'a', True: 'b', 1.0: 'c'} B)
{1: 'c'} C)
{1: 'c', True: 'b'} D)
KeyError✅ Правильный ответ:
Разбор
Ключи в словаре сравниваются не по типу, а по
__hash__ и __eq__.bool — подкласс int, поэтому True == 1, и hash(True) == hash(1). У float та же история: 1.0 == 1, и hash(1.0) == hash(1).Для Python это три записи с одинаковым ключом. Каждая следующая просто перезаписывает значение по уже существующему хешу:
d = {}
d[1] = 'a' # новый ключ
d[True] = 'b' # ключ уже есть → перезаписываем значение
d[1.0] = 'c' # ключ уже есть → перезаписываем значениеВажный нюанс: значение перезаписывается, а вот объект ключа — нет. CPython хранит тот ключ, который был вставлен первым. Поэтому в итоговом словаре ключ — это
int(1), а не True и не 1.0, хотя последним «выигрывает» именно их значение.Вывод
Если используешь
int, bool и float как ключи словаря или элементы множества — они не так изолированы, как кажется. 1, True и 1.0 для хеш-таблицы это один и тот же ключ. На собеседовании это отличный способ проверить, понимает ли кандидат, что равенство и хеш решают, а не тип объекта.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍2
🧠 Interview Thinking
Задача
Дан список интервалов
👶 Как думает junior
Первая мысль — сравнить каждый интервал с каждым и сливать на лету.
Работает. Но это
🧠 Как думает сильный кандидат
Ключевая идея: если отсортировать интервалы по началу, пересекаться друг с другом смогут только соседние элементы. Тогда достаточно одного прохода.
🎯 Что хочет интервьюер
Не код сам по себе. Интервьюер проверяет:
- заметил ли кандидат, что сортировка убирает необходимость сравнивать всё со всем;
- понимает ли, почему после сортировки достаточно смотреть только на соседа;
- задаёт ли уточняющие вопросы: интервалы закрытые или открытые?
- готов ли явно назвать trade-off: сортировка стоит
💡 Вывод
Разница между junior и сильным кандидатом здесь не в синтаксисе, а в одном наблюдении: сортировка — это не «дополнительная работа», а способ превратить задачу «сравнить всё со всем» в задачу «сравнить с соседом». Это наблюдение стоит проговаривать вслух — интервьюер должен услышать ход мысли, а не увидеть готовый код.
🐍Вопросы с собесов -> ProstoPython
Задача
Дан список интервалов
[[1, 3], [2, 6], [8, 10], [15, 18]]. Нужно объединить все пересекающиеся интервалы.👶 Как думает junior
Первая мысль — сравнить каждый интервал с каждым и сливать на лету.
def merge(intervals):
result = intervals[:]
merged = True
while merged:
merged = False
for i in range(len(result)):
for j in range(i + 1, len(result)):
a, b = result[i], result[j]
if a[0] <= b[1] and b[0] <= a[1]:
result[i] = [min(a[0], b[0]), max(a[1], b[1])]
result.pop(j)
merged = True
break
if merged:
break
return result
Работает. Но это
O(n²) в среднем, а в худшем — и того хуже: мутация списка внутри вложенных циклов + перезапуск после каждого слияния.🧠 Как думает сильный кандидат
Ключевая идея: если отсортировать интервалы по началу, пересекаться друг с другом смогут только соседние элементы. Тогда достаточно одного прохода.
def merge(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
result = [intervals[0]]
for start, end in intervals[1:]:
last_end = result[-1][1]
if start <= last_end:
result[-1][1] = max(last_end, end)
else:
result.append([start, end])
return result
O(n log n) за счёт сортировки, дальше — линейный проход без вложенных циклов и без мутации списка на лету.🎯 Что хочет интервьюер
Не код сам по себе. Интервьюер проверяет:
- заметил ли кандидат, что сортировка убирает необходимость сравнивать всё со всем;
- понимает ли, почему после сортировки достаточно смотреть только на соседа;
- задаёт ли уточняющие вопросы: интервалы закрытые или открытые?
[1, 3] и [3, 5] — это пересечение или нет? может ли список быть пустым?- готов ли явно назвать trade-off: сортировка стоит
O(n log n), но избавляет от квадратичной сложности и лишней мутации.💡 Вывод
Разница между junior и сильным кандидатом здесь не в синтаксисе, а в одном наблюдении: сортировка — это не «дополнительная работа», а способ превратить задачу «сравнить всё со всем» в задачу «сравнить с соседом». Это наблюдение стоит проговаривать вслух — интервьюер должен услышать ход мысли, а не увидеть готовый код.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥2
📈 From O(...) to O(...)
Задача
Найти длину самой длинной подстроки без повторяющихся символов. Например, для
❌ Наивное решение
Проверяем каждую подстроку на уникальность символов.
Сложность:
✅ Оптимизированное решение
Скользящее окно + хеш-таблица, которая хранит последнюю позицию каждого символа.
Сложность:
💡 Что изменилось
Наивное решение каждый раз пересчитывает уникальность окна с нуля, хотя между соседними окнами меняется всего один символ — вся остальная информация просто выбрасывается.
Идея оптимизации: не пересчитывать, а поддерживать состояние.
Ключевой момент —
⚡ Вывод
Как только видишь задачу вида «найти лучшее окно/подстроку/подмассив с условием» — первый вопрос должен быть не «как перебрать все варианты», а «что можно инкрементально поддерживать при сдвиге границы окна». В 90% случаев это словарь или множество с позициями/счётчиками, и он превращает перебор в один проход.
🐍Вопросы с собесов -> ProstoPython
Задача
Найти длину самой длинной подстроки без повторяющихся символов. Например, для
"abcabcbb" ответ — 3 (`"abc"`).❌ Наивное решение
Проверяем каждую подстроку на уникальность символов.
def longest_unique(s):
n = len(s)
best = 0
for i in range(n):
for j in range(i, n):
window = s[i:j + 1]
if len(set(window)) == len(window):
best = max(best, len(window))
return best
Сложность:
O(n³) — O(n²) подстрок, и ещё O(n) уходит на set(window) для проверки каждой.✅ Оптимизированное решение
Скользящее окно + хеш-таблица, которая хранит последнюю позицию каждого символа.
def longest_unique(s):
last_seen = {}
left = 0
best = 0
for right, char in enumerate(s):
if char in last_seen and last_seen[char] >= left:
left = last_seen[char] + 1
last_seen[char] = right
best = max(best, right - left + 1)
return best
Сложность:
O(n) — каждый символ обрабатывается ровно один раз, left двигается только вперёд.💡 Что изменилось
Наивное решение каждый раз пересчитывает уникальность окна с нуля, хотя между соседними окнами меняется всего один символ — вся остальная информация просто выбрасывается.
Идея оптимизации: не пересчитывать, а поддерживать состояние.
last_seen хранит, где в последний раз встретился каждый символ. Как только текущий символ повторяется внутри текущего окна, левая граница сразу прыгает за место его прошлого появления — без перебора символов между left и повтором.Ключевой момент —
right никогда не уменьшается, left тоже двигается только вперёд. Значит, суммарно оба указателя пройдут по строке не больше 2n раз, а не n².⚡ Вывод
Как только видишь задачу вида «найти лучшее окно/подстроку/подмассив с условием» — первый вопрос должен быть не «как перебрать все варианты», а «что можно инкрементально поддерживать при сдвиге границы окна». В 90% случаев это словарь или множество с позициями/счётчиками, и он превращает перебор в один проход.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍2