⏱️ Big O Breakdown
Убираем дубликаты, сохраняя порядок.
Какая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(n) в среднем
Правильный ответ:C — O(n²)
Разбор
Глаз цепляется за один цикл
И это сравнение делается на каждой из
Один видимый цикл, но
🐍Вопросы с собесов -> ProstoPython
def dedup(items):
result = []
for x in items:
if x not in result:
result.append(x)
return result
Убираем дубликаты, сохраняя порядок.
n — длина items.Какая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(n) в среднем
Правильный ответ:
Разбор
Глаз цепляется за один цикл
for и думает «O(n)». Но настоящая работа спрятана в x not in result.in по списку — это линейный поиск. Python проходит элементы один за другим, пока не найдёт совпадение:x not in result → до n сравнений
И это сравнение делается на каждой из
n итераций:итерация 1: поиск среди 0 элементов
итерация 2: поиск среди 1
итерация 3: поиск среди 2
...
итерация n: поиск среди n-1
всего: 0 + 1 + 2 + ... + (n-1) = n(n-1)/2 → O(n²)
Один видимый цикл, но
in прячет второй внутри себя.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
❌ Rookie Mistakes
Хотим выкинуть нулевые значения. Логика очевидна. А Python падает:
Почему это ошибка
Это сделано специально: продолжать итерацию по изменившейся хеш-таблице небезопасно — можно пропустить элементы или пройти один дважды. Лучше явный краш, чем тихо неверный результат.
🐍Вопросы с собесов -> ProstoPython
counts = {"a": 0, "b": 3, "c": 0, "d": 5}
for key in counts:
if counts[key] == 0:
del counts[key]Хотим выкинуть нулевые значения. Логика очевидна. А Python падает:
RuntimeError: dictionary changed size during iteration
Почему это ошибка
for key in counts не делает копию ключей. Он держит живой итератор по самому словарю. Как только ты удаляешь элемент — размер меняется, итератор обнаруживает это и аварийно останавливается.читаем "a" → del "a" → размер изменился → 💥
Это сделано специально: продолжать итерацию по изменившейся хеш-таблице небезопасно — можно пропустить элементы или пройти один дважды. Лучше явный краш, чем тихо неверный результат.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
🧰 Code Cleanup
Плохой код
Группируем юзеров по городам. Работает, но каждая запись — это три строки ритуала: проверить ключ, создать пустой список, и только потом добавить.
Чистый вариант
🐍Вопросы с собесов -> ProstoPython
Плохой код
groups = {}
for user in users:
if user.city not in groups:
groups[user.city] = []
groups[user.city].append(user.name)Группируем юзеров по городам. Работает, но каждая запись — это три строки ритуала: проверить ключ, создать пустой список, и только потом добавить.
Чистый вариант
from collections import defaultdict
groups = defaultdict(list)
for user in users:
groups[user.city].append(user.name)
defaultdict(list) сам создаёт пустой список при первом обращении к новому ключу. Проверка if ... not in исчезает — её делает сама структура.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
🧠 Что выведет код
Варианты:
A)
B)
C)
D)
Правильный ответ: B — [2, 2, 2]
Классика, на которой спотыкаются почти все.
Разбор
Кажется, что каждая лямбда «запоминает» своё
Лямбда не сохраняет значение
Это называется late binding: имя внутри замыкания разрешается поздно — при вызове.
К моменту
Как починить — «заморозить» значение
Передать
Теперь каждая лямбда несёт свою копию.
🐍Вопросы с собесов -> ProstoPython
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
Варианты:
A)
[0, 1, 2] B)
[2, 2, 2] C)
[3, 3, 3] D)
[0, 0, 0]Правильный ответ:
Классика, на которой спотыкаются почти все.
Разбор
Кажется, что каждая лямбда «запоминает» своё
i. На самом деле — нет.Лямбда не сохраняет значение
i. Она сохраняет ссылку на переменную i и смотрит на неё только в момент вызова, а не в момент создания.создаём лямбды: i крутится 0 → 1 → 2
все три лямбды ссылаются на одну и ту же i
вызываем f(): цикл давно закончился, i == 2
все три читают i → 2, 2, 2
Это называется late binding: имя внутри замыкания разрешается поздно — при вызове.
К моменту
f() цикл отработал полностью, и i навсегда застряла на последнем значении 2.Как починить — «заморозить» значение
Передать
i как аргумент со значением по умолчанию (оно вычисляется сразу, в момент создания функции):funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
Теперь каждая лямбда несёт свою копию.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
🧠 Interview Thinking
Задача
В массиве каждое число встречается дважды, кроме одного — оно встречается один раз. Найди его.
Пример:
Как думает junior
«Посчитаю, сколько раз встречается каждое число, и верну то, у которого счётчик
Корректно. O(n) время, но O(n) память — храним весь словарь.
На собесе после этого почти всегда летит вопрос: «А можешь без дополнительной памяти?»
Как думает сильный кандидат
Инсайт: тут просится XOR (
У XOR два свойства, которые решают задачу целиком:
А ещё XOR коммутативен — порядок не важен. Значит, если проксорить все числа подряд, каждая пара схлопнется в
O(n) время, O(1) память. Ни словаря, ни сортировки.
🐍Вопросы с собесов -> ProstoPython
Задача
В массиве каждое число встречается дважды, кроме одного — оно встречается один раз. Найди его.
Пример:
[4, 1, 2, 1, 2] → 4.Как думает junior
«Посчитаю, сколько раз встречается каждое число, и верну то, у которого счётчик
1.»from collections import Counter
def single_number(nums):
counts = Counter(nums)
for num, c in counts.items():
if c == 1:
return num
Корректно. O(n) время, но O(n) память — храним весь словарь.
На собесе после этого почти всегда летит вопрос: «А можешь без дополнительной памяти?»
Как думает сильный кандидат
Инсайт: тут просится XOR (
^).У XOR два свойства, которые решают задачу целиком:
x ^ x = 0 число, ксоренное само с собой, обнуляется
x ^ 0 = x ксор с нулём ничего не меняет
А ещё XOR коммутативен — порядок не важен. Значит, если проксорить все числа подряд, каждая пара схлопнется в
0, и останется только одиночка:4 ^ 1 ^ 2 ^ 1 ^ 2
= 4 ^ (1 ^ 1) ^ (2 ^ 2)
= 4 ^ 0 ^ 0
= 4
from functools import reduce
from operator import xor
def single_number(nums):
return reduce(xor, nums)
O(n) время, O(1) память. Ни словаря, ни сортировки.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
🎭 Red Flag
Плохой пример
Выглядит «надёжно»: что бы ни случилось — функция не упадёт, вернёт пустой конфиг. Программа продолжит работать.
Именно в этом и проблема.
Что не так
Все эти разные беды слиплись в один тихий «всё нормально». Приложение поедет на пустом конфиге, а настоящую причину ты будешь искать часами — потому что в логах пусто.
Хуже того:
Как надо
Лови конкретные ожидаемые исключения, остальное пусть летит наверх:
Теперь видно: чего ты ожидал (файла может не быть — ок) и что должно громко падать (битый JSON — это ошибка деплоя, а не норма).
🐍Вопросы с собесов -> ProstoPython
Плохой пример
def get_config(path):
try:
with open(path) as f:
return json.load(f)
except Exception:
pass
return {}
Выглядит «надёжно»: что бы ни случилось — функция не упадёт, вернёт пустой конфиг. Программа продолжит работать.
Именно в этом и проблема.
Что не так
except Exception ловит всё подряд, а pass молча проглатывает. Ты теряешь информацию о том, что вообще пошло не так:файла нет → молчим, отдаём {}
битый JSON → молчим, отдаём {}
опечатка в коде → молчим, отдаём {} ← а вот это уже баг!
нет прав на чтение → молчим, отдаём {}Все эти разные беды слиплись в один тихий «всё нормально». Приложение поедет на пустом конфиге, а настоящую причину ты будешь искать часами — потому что в логах пусто.
Хуже того:
except Exception способна поймать KeyError/AttributeError от твоей же опечатки внутри try — и баг превратится в невидимку.Как надо
Лови конкретные ожидаемые исключения, остальное пусть летит наверх:
def get_config(path):
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
logging.warning("Конфиг %s не найден, беру дефолт", path)
return {}
except json.JSONDecodeError as e:
logging.error("Конфиг %s битый: %s", path, e)
raise # это уже серьёзно — не глотаем
Теперь видно: чего ты ожидал (файла может не быть — ок) и что должно громко падать (битый JSON — это ошибка деплоя, а не норма).
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
⏱️ Big O Breakdown
Для каждого нового числа считаем медиану по всему, что пришло.
Какая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(n² log n)
Правильный ответ:D — O(n² log n)
Почти все говорят C, забывая про логарифм от сортировки.
Разбор
Считаем по слоям.
Цикл крутится
Суммируем по всем итерациям:
Две ловушки сразу:
C (O(n²)) получился бы, будь внутри линейная операция (
Где это легко прозевать
🐍Вопросы с собесов -> ProstoPython
def running_medians(stream):
seen = []
result = []
for x in stream:
seen.append(x)
s = sorted(seen)
result.append(s[len(s) // 2])
return result
Для каждого нового числа считаем медиану по всему, что пришло.
n — длина потока.Какая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(n² log n)
Правильный ответ:
Почти все говорят C, забывая про логарифм от сортировки.
Разбор
Считаем по слоям.
Цикл крутится
n раз. На каждой итерации внутри прячется sorted(seen):итерация i: сортируем список длины i → O(i · log i)
Суммируем по всем итерациям:
i=1: 1·log1
i=2: 2·log2
...
i=n: n·log n
каждое слагаемое ≤ n·log n, а слагаемых n штук
→ всего ≤ n · (n log n) = O(n² log n)
Две ловушки сразу:
1) sorted внутри цикла → лишний множитель n
2) сама сортировка → лишний множитель log n
C (O(n²)) получился бы, будь внутри линейная операция (
sum, max). Но сортировка дороже — отсюда log n сверху.Где это легко прозевать
sorted(seen) — короткая строка, читается как «ну, отсортировали». Но она пересортировывает весь накопленный список заново на каждом шаге, хотя добавился всего один элемент.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
⚖️ This vs That: is vs ==
Путают постоянно. А разница — фундаментальная, и на ней ловят на собесах.
Что делает
Сравнивает значения: «равны ли эти объекты по содержимому». Вызывает метод
Что делает
Сравнивает идентичность: «это один и тот же объект в памяти». Сравнивает
Главное отличие в одной фразе
🐍Вопросы с собесов -> ProstoPython
Путают постоянно. А разница — фундаментальная, и на ней ловят на собесах.
Что делает
==Сравнивает значения: «равны ли эти объекты по содержимому». Вызывает метод
__eq__.a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — содержимое одинаковое
Что делает
isСравнивает идентичность: «это один и тот же объект в памяти». Сравнивает
id(), никаких методов не зовёт.a is b # False — два разных списка, просто с одинаковым содержимым
a ──► [1, 2, 3] id = 0xAAA
b ──► [1, 2, 3] id = 0xBBB
a == b → сравнивает содержимое → True
a is b → сравнивает адреса → False
Главное отличие в одной фразе
==спрашивает «равны ли они?»,
is— «это вообще один и тот же объект?».
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
📈 From O(n·m) to O(n+m)
Задача
Даны два отсортированных массива. Слей их в один отсортированный.
Пример:
Наивное решение
«Соединю и отсортирую — делов-то»
Работает, коротко. Но мы выбросили главное — то, что массивы уже отсортированы.
Проблема
Платим за сортировку, хотя данные уже почти готовы. Логарифмический множитель здесь лишний — порядок внутри каждого массива нам ничего не стоил, а мы его проигнорировали.
Оптимизированное решение
Два указателя. Идём по обоим массивам одновременно, на каждом шаге берём меньший из текущих элементов.
Каждый элемент трогаем ровно один раз → O(n+m) время.
Объяснение
Трассировка
Указатели только движутся вперёд и никогда не откатываются — отсюда линейность.
🐍Вопросы с собесов -> ProstoPython
Задача
Даны два отсортированных массива. Слей их в один отсортированный.
Пример:
[1, 3, 5] и [2, 4, 6] → [1, 2, 3, 4, 5, 6].Наивное решение
«Соединю и отсортирую — делов-то»
def merge(a, b):
return sorted(a + b)
Работает, коротко. Но мы выбросили главное — то, что массивы уже отсортированы.
sorted об этом не знает и сортирует с нуля: O((n+m)·log(n+m)).Проблема
Платим за сортировку, хотя данные уже почти готовы. Логарифмический множитель здесь лишний — порядок внутри каждого массива нам ничего не стоил, а мы его проигнорировали.
Оптимизированное решение
Два указателя. Идём по обоим массивам одновременно, на каждом шаге берём меньший из текущих элементов.
def merge(a, b):
i = j = 0
result = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
# один из массивов кончился — добиваем хвост второго
result.extend(a[i:])
result.extend(b[j:])
return result
Каждый элемент трогаем ровно один раз → O(n+m) время.
Объяснение
Трассировка
a = [1, 3, 5], b = [2, 4, 6]:a: [1, 3, 5] b: [2, 4, 6]
i j
1 ≤ 2 → берём 1, i→ result: [1]
3 > 2 → берём 2, j→ result: [1, 2]
3 ≤ 4 → берём 3, i→ result: [1, 2, 3]
5 > 4 → берём 4, j→ result: [1, 2, 3, 4]
5 ≤ 6 → берём 5, i→ result: [1, 2, 3, 4, 5]
a кончился → добиваем хвост b: [6]
result: [1, 2, 3, 4, 5, 6]
Указатели только движутся вперёд и никогда не откатываются — отсюда линейность.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3
❌ Rookie Mistakes
Кажется логичным: не передал корзину — получи пустую. Но запусти дважды:
Банан попал в ту же корзину, что и яблоко. Хотя мы её вроде не передавали.
Почему это ошибка
Значение по умолчанию вычисляется один раз — в момент определения функции, а не при каждом вызове.
Все вызовы без аргумента используют этот единственный список. Мутируешь его в одном вызове — изменения видны во всех последующих.
Список живёт между вызовами, как глобальная переменная, о которой ты не просил.
Кусаются именно мутабельные дефолты:
Исправленный вариант
Дефолтом ставь
🐍Вопросы с собесов -> ProstoPython
def add_item(item, basket=[]):
basket.append(item)
return basket
Кажется логичным: не передал корзину — получи пустую. Но запусти дважды:
add_item("apple") # ['apple']
add_item("banana") # ['apple', 'banana'] ← откуда яблоко?!Банан попал в ту же корзину, что и яблоко. Хотя мы её вроде не передавали.
Почему это ошибка
Значение по умолчанию вычисляется один раз — в момент определения функции, а не при каждом вызове.
def add_item(...basket=[]): ← здесь создаётся ОДИН список
и привязывается к функции навсегда
Все вызовы без аргумента используют этот единственный список. Мутируешь его в одном вызове — изменения видны во всех последующих.
вызов 1: basket → [тот самый список] → append("apple") → ['apple']
вызов 2: basket → [тот же список!] → append("banana") → ['apple', 'banana']Список живёт между вызовами, как глобальная переменная, о которой ты не просил.
Кусаются именно мутабельные дефолты:
[], {}, set(). С неизменяемыми (None, числа, строки, кортежи) проблемы нет — их и так нельзя поменять на месте.Исправленный вариант
Дефолтом ставь
None, а реальный объект создавай внутри — он будет свежим на каждый вызов:def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
add_item("apple") # ['apple']
add_item("banana") # ['banana'] ← теперь каждый раз новая корзина
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🏆4
🧰 Code Cleanup
Плохой код
Логика простая, а читать невозможно: четыре уровня вложенности, «лесенка», и где какой
Чистый вариант
Те же условия — но «плохие» случаи отсекаем сразу и выходим. Что осталось до конца функции — это счастливый путь.
Объяснение
Приём называется guard clauses (защитные проверки): вместо того чтобы заворачивать основную логику во вложенные
Что меняется:
🐍Вопросы с собесов -> ProstoPython
Плохой код
def get_discount(user):
if user is not None:
if user.is_active:
if user.subscription is not None:
if user.subscription.is_premium:
return 0.2
else:
return 0.1
else:
return 0.0
else:
return 0.0
return 0.0
Логика простая, а читать невозможно: четыре уровня вложенности, «лесенка», и где какой
else относится — глаза ломаются. Главное действие утонуло в самой глубине.Чистый вариант
def get_discount(user):
if user is None:
return 0.0
if not user.is_active:
return 0.0
if user.subscription is None:
return 0.0
return 0.2 if user.subscription.is_premium else 0.1
Те же условия — но «плохие» случаи отсекаем сразу и выходим. Что осталось до конца функции — это счастливый путь.
Объяснение
Приём называется guard clauses (защитные проверки): вместо того чтобы заворачивать основную логику во вложенные
if, ты в начале функции отбрасываешь всё, что мешает, через ранний return.Что меняется:
было: условие → углубляемся → условие → углубляемся → …
(читать надо, держа в голове весь стек вложенности)
стало: не то? → выход.
не то? → выход.
дошли сюда — значит всё ок, делаем дело.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3🔥1
🧠 Что выведет код
Варианты:
A)
B)
C)
D)
Правильный ответ:B
Меняли одну ячейку — «поплыла» вся колонка. На этом теряют часы отладки.
Разбор
Тут две разные
Поэтому
🐍Вопросы с собесов -> ProstoPython
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
Варианты:
A)
[[1, 0, 0], [0, 0, 0], [0, 0, 0]] B)
[[1, 0, 0], [1, 0, 0], [1, 0, 0]] C)
[[1, 1, 1], [0, 0, 0], [0, 0, 0]] D)
ОшибкаПравильный ответ:
Меняли одну ячейку — «поплыла» вся колонка. На этом теряют часы отладки.
Разбор
Тут две разные
* 3, и они делают не одно и то же.[0] * 3 → [0, 0, 0] три НОВЫХ нуля (числа неизменяемы, копий не надо)
[...] * 3 → три ссылки на ОДИН И ТОТ ЖЕ список
* 3 для внешнего списка не копирует внутренний. Он трижды кладёт ссылку на один объект:grid ──► [ • , • , • ]
│ │ │
└───┴───┘
▼
[0, 0, 0] ← одна строка на всех
Поэтому
grid[0], grid[1], grid[2] — это один и тот же список. Меняешь через любой — видишь во всех.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
🧠 Interview Thinking
Задача
Дан массив. Передвинь все нули в конец, сохранив порядок остальных элементов. Меняй на месте, без нового массива.
Пример:
Как думает junior
«Соберу ненулевые в новый список, потом добью нулями.»
Логика верная. Но условие сказало на месте — а мы завели новый массив, O(n) доп. памяти. И ничего не меняем в исходном (вернули новый).
На собесе тут же спросят: «А без выделения второго массива?»
Как думает сильный кандидат
Инсайт: не «двигать нули», а подтягивать ненулевые вперёд. Нули окажутся в конце сами.
Два указателя, оба в одном массиве:
O(n) время, O(1) память. Меняем исходный массив.
🐍Вопросы с собесов -> ProstoPython
Задача
Дан массив. Передвинь все нули в конец, сохранив порядок остальных элементов. Меняй на месте, без нового массива.
Пример:
[0, 1, 0, 3, 12] → [1, 3, 12, 0, 0].Как думает junior
«Соберу ненулевые в новый список, потом добью нулями.»
def move_zeroes(nums):
result = [x for x in nums if x != 0]
result += [0] * (len(nums) - len(result))
return result
Логика верная. Но условие сказало на месте — а мы завели новый массив, O(n) доп. памяти. И ничего не меняем в исходном (вернули новый).
На собесе тут же спросят: «А без выделения второго массива?»
Как думает сильный кандидат
Инсайт: не «двигать нули», а подтягивать ненулевые вперёд. Нули окажутся в конце сами.
Два указателя, оба в одном массиве:
insert — куда класть следующий ненулевой элемент;i — бежит по массиву и ищет ненулевые.def move_zeroes(nums):
insert = 0
for i in range(len(nums)):
if nums[i] != 0:
nums[insert], nums[i] = nums[i], nums[insert]
insert += 1
O(n) время, O(1) память. Меняем исходный массив.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
⏱️ Big O Breakdown
Обрабатываем задачи по очереди — берём из начала списка, пока не опустеет.
Какая общая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(1)
Правильный ответ:C — O(n²)
Цикл выглядит линейным, но
Разбор
Убрал нулевой элемент → дырка в начале → все остальные надо сдвинуть на одну ячейку влево:
🐍Вопросы с собесов -> ProstoPython
def process_queue(tasks):
while tasks:
task = tasks.pop(0) # берём из начала
handle(task)
Обрабатываем задачи по очереди — берём из начала списка, пока не опустеет.
n — число задач.Какая общая сложность по времени?
A) O(n)
B) O(n log n)
C) O(n²)
D) O(1)
Правильный ответ:
Цикл выглядит линейным, но
pop(0) всё портит.Разбор
list.pop(0) берёт первый элемент. Но список в Python — это массив: элементы лежат подряд в памяти, индексы привязаны к позиции.Убрал нулевой элемент → дырка в начале → все остальные надо сдвинуть на одну ячейку влево:
[A, B, C, D]
↑ pop(0)
убираем A, сдвигаем хвост:
[B, C, D]
└─ B, C, D переехали ← это O(n)
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
⚖️ This vs That: yield vs return
Оба «отдают» значение из функции. Но
Что делает
Возвращает значение и завершает функцию. Всё, она отработала, состояние стёрто.
Что делает
Отдаёт значение и ставит функцию на паузу, запоминая, где остановилась. При следующем запросе — продолжает с того же места.
Функция с
Главное отличие в одной фразе
🐍Вопросы с собесов -> ProstoPython
Оба «отдают» значение из функции. Но
yield превращает функцию в совсем другого зверя.Что делает
returnВозвращает значение и завершает функцию. Всё, она отработала, состояние стёрто.
def get_numbers():
return [1, 2, 3] # посчитали ВЕСЬ список сразу, отдали, вышли
Что делает
yieldОтдаёт значение и ставит функцию на паузу, запоминая, где остановилась. При следующем запросе — продолжает с того же места.
def get_numbers():
yield 1 # отдал 1, замер
yield 2 # продолжил, отдал 2, замер
yield 3
Функция с
yield — это генератор: значения выдаются по одному, лениво, по требованию.return: [█████████] всё посчитано и лежит в памяти целиком
yield: █ → █ → █ по одному, считается ровно когда нужно
Главное отличие в одной фразе
returnотдаёт всё сразу и забывает функцию.
yieldотдаёт по одному и помнит, где остановился.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
❌ Rookie Mistakes
Сравнение float через
Логично же:
Python считает, что
Почему это ошибка
Компьютер хранит дробные числа в двоичной системе. А
Это не баг Python — так устроена арифметика с плавающей точкой везде (стандарт IEEE 754). В Java, C, JS — ровно то же самое.
🐍Вопросы с собесов -> ProstoPython
Сравнение float через
==total = 0.1 + 0.2
if total == 0.3:
print("оплата прошла")
else:
print("ошибка суммы")
Логично же:
0.1 + 0.2 это 0.3. Запускаем:ошибка суммы
Python считает, что
0.1 + 0.2 != 0.3. Проверим в лоб:>>> 0.1 + 0.2
0.30000000000000004
Почему это ошибка
Компьютер хранит дробные числа в двоичной системе. А
0.1, 0.2, 0.3 в двоичной — это бесконечные дроби (как 1/3 = 0.333... в десятичной). Их приходится обрезать под размер float.0.1 → хранится как ≈ 0.1000000000000000055...
0.2 → хранится как ≈ 0.2000000000000000111...
сумма → ≈ 0.3000000000000000444...
а 0.3 само по себе → ≈ 0.2999999999999999888...
0.30000...04 ≠ 0.29999...88 → False
Это не баг Python — так устроена арифметика с плавающей точкой везде (стандарт IEEE 754). В Java, C, JS — ровно то же самое.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
📈 From O(n²) to O(n)
Задача
Дана строка. Найди длину самой длинной подстроки, в которой нет повторяющихся символов.
Пример:
Наивное решение
«Проверю все подстроки: для каждого начала тяну конец, пока символы не повторятся.»
Корректно. Но для каждого старта
Проблема
Когда натыкаемся на повтор, мы выбрасываем всю работу и начинаем со следующего символа с нуля. А ведь часть уже проверенного окна всё ещё валидна — её незачем пересчитывать.
Оптимизированное решение
Скользящее окно (sliding window). Держим окно
O(n) время: каждый символ обрабатывается один раз.
🐍Вопросы с собесов -> ProstoPython
Задача
Дана строка. Найди длину самой длинной подстроки, в которой нет повторяющихся символов.
Пример:
"abcabcbb" → 3 (это "abc").Наивное решение
«Проверю все подстроки: для каждого начала тяну конец, пока символы не повторятся.»
def longest_unique(s):
best = 0
for i in range(len(s)):
seen = set()
for j in range(i, len(s)):
if s[j] in seen:
break
seen.add(s[j])
best = max(best, j - i + 1)
return best
Корректно. Но для каждого старта
i мы заново бежим вправо — O(n²).Проблема
Когда натыкаемся на повтор, мы выбрасываем всю работу и начинаем со следующего символа с нуля. А ведь часть уже проверенного окна всё ещё валидна — её незачем пересчитывать.
Оптимизированное решение
Скользящее окно (sliding window). Держим окно
[left..right] без повторов. Двигаем right вперёд; если символ уже в окне — подтягиваем left, выкидывая «хвост» до дубликата.def longest_unique(s):
seen = {} # символ → его последний индекс
left = 0
best = 0
for right, ch in enumerate(s):
if ch in seen and seen[ch] >= left:
left = seen[ch] + 1 # сдвигаем левую границу за дубликат
seen[ch] = right
best = max(best, right - left + 1)
return best
O(n) время: каждый символ обрабатывается один раз.
left и right только движутся вперёд.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
🧠 Что выведет код
Варианты:
A)
B)
C)
D)
Правильный ответ: D )
Снаружи существует. А функция всё равно падает. Почему?
Разбор
Python определяет, локальная переменная или глобальная, не во время выполнения, а заранее — на этапе компиляции функции.
Правило: если внутри функции переменной где-то присваивают значение — она считается локальной на всю функцию целиком. С первой строки до последней.
Из-за
А на строке
Ключевой момент: дело не в порядке строк сверху вниз. Даже если
🐍Вопросы с собесов -> ProstoPython
def f():
print(x)
x = 10
x = 5
f()
Варианты:
A)
5 B)
10 C)
None D)
UnboundLocalErrorПравильный ответ:
x = 5Разбор
Python определяет, локальная переменная или глобальная, не во время выполнения, а заранее — на этапе компиляции функции.
Правило: если внутри функции переменной где-то присваивают значение — она считается локальной на всю функцию целиком. С первой строки до последней.
def f():
print(x) # ← пытаемся прочитать x
x = 10 # ← из-за этой строки x ЛОКАЛЬНА во всей f
Из-за
x = 10 ниже Python ещё при компиляции решает: «x здесь локальная». Глобальная x = 5 для функции больше не видна — её заслонила локальная.А на строке
print(x) локальной x ещё не присвоили значение:f() вызвана
↓
x объявлена локальной (из-за присваивания ниже)
↓
print(x) → читаем локальную x, которой ещё нет → 💥 UnboundLocalError
Ключевой момент: дело не в порядке строк сверху вниз. Даже если
print(x) идёт первым — наличие присваивания где угодно в функции делает переменную локальной везде.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
🧰 Code Cleanup
Плохой код
Работает. Но это «лестница»: каждая новая команда — ещё два
Чистый вариант
Соответствие «команда → функция» стало данными, а не кодом. Добавить команду — одна строка в словарь, а не новая ветка.
Объяснение
Длинный
🐍Вопросы с собесов -> ProstoPython
Плохой код
def get_handler(command):
if command == "start":
return start_game()
elif command == "pause":
return pause_game()
elif command == "resume":
return resume_game()
elif command == "stop":
return stop_game()
else:
return unknown_command()
Работает. Но это «лестница»: каждая новая команда — ещё два
elif. И вся структура — однотипная: сравнили строку → вызвали функцию.Чистый вариант
HANDLERS = {
"start": start_game,
"pause": pause_game,
"resume": resume_game,
"stop": stop_game,
}
def get_handler(command):
handler = HANDLERS.get(command, unknown_command)
return handler()Соответствие «команда → функция» стало данными, а не кодом. Добавить команду — одна строка в словарь, а не новая ветка.
Объяснение
Длинный
if/elif, который сравнивает одну и ту же переменную с разными значениями — это замаскированная таблица соответствий. А таблицу естественнее хранить в словаре.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
🧠 Interview Thinking
Задача
Даны две строки. Определи, являются ли они анаграммами — то есть состоят из одних и тех же символов в одинаковом количестве.
Пример:
Как думает junior
«Анаграммы — это одинаковый набор букв. Отсортирую обе строки и сравню.»
Коротко и работает. Это абсолютно валидный ответ — не стыдись его. Но сортировка — это O(n log n), и на собесе спросят: «А можешь за линию?»
Как думает сильный кандидат
Инсайт: мне не нужен порядок букв — нужно только сколько раз каждая встречается. А подсчёт — это
Считаю частоты в обеих строках и сравниваю:
O(n) время.
Хочешь показать, что понимаешь, что под капотом, — можно и руками одним словарём:
🐍Вопросы с собесов -> ProstoPython
Задача
Даны две строки. Определи, являются ли они анаграммами — то есть состоят из одних и тех же символов в одинаковом количестве.
Пример:
"listen" и "silent" → True; "rat" и "car" → False.Как думает junior
«Анаграммы — это одинаковый набор букв. Отсортирую обе строки и сравню.»
def is_anagram(s, t):
return sorted(s) == sorted(t)
Коротко и работает. Это абсолютно валидный ответ — не стыдись его. Но сортировка — это O(n log n), и на собесе спросят: «А можешь за линию?»
Как думает сильный кандидат
Инсайт: мне не нужен порядок букв — нужно только сколько раз каждая встречается. А подсчёт — это
O(n), без всякой сортировки.Считаю частоты в обеих строках и сравниваю:
from collections import Counter
def is_anagram(s, t):
return Counter(s) == Counter(t)
O(n) время.
Counter строит словарь {буква: количество}, а == сравнивает их целиком.Хочешь показать, что понимаешь, что под капотом, — можно и руками одним словарём:
def is_anagram(s, t):
if len(s) != len(t): # разной длины — точно не анаграммы
return False
counts = {}
for ch in s:
counts[ch] = counts.get(ch, 0) + 1
for ch in t:
if ch not in counts:
return False
counts[ch] -= 1
if counts[ch] == 0:
del counts[ch]
return not counts # пусто → всё сошлось
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4