Prosto Python | вопросы с собесов
363 subscribers
184 photos
1 video
2 files
562 links
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
Download Telegram
⏱️ Big O Breakdown

Посмотри на код:
def example(n):
for i in range(n):
j = 1
while j < n:
print(i, j)
j *= 2


Какая здесь сложность?

A) O(n)
B) O(n log n)
C) O(n²)
D) O(log n)

Правильный ответ: B) O(n log n)

🧠 Разбор

Внешний цикл:

for i in range(n)


выполняется n раз → O(n)

Внутренний цикл:

j *= 2


значение растёт так:

1 —> 2 —> 4 —> 8 —> 16 —> ...


Количество шагов примерно log₂(n) —> O(log n)

Теперь объединяем:

O(n) * O(log n) = O(n log n)


🐍Вопросы с собесов -> ProstoPython
🔥4
🧰 Code Cleanup — выпуск 28
Тема: лишний вызов len()

Иногда код делает одну и ту же работу несколько раз.


Плохо:
def get_last(nums):
if len(nums) > 0:
return nums[len(nums) - 1]
return None


Работает.
Но выглядит тяжелее, чем должен.

Code Cleanup:
def get_last(nums):
if nums:
return nums[-1]
return None


🧠 Почему так лучше


🔹 if nums — питоничный способ проверить пустоту
🔹 nums[-1] сразу берёт последний элемент
🔹 код короче и читается быстрее
🔹 не вызываем len() лишний раз

🐍Вопросы с собесов -> ProstoPython
👍4
📈 From O(n log n) to O(n)

Задача: проверить, является ли строка анаграммой другой строки.
(то есть состоят ли строки из одинаковых символов)


Например:

"listen" и "silent" → True



Популярное решение — O(n log n)
def is_anagram(s, t):
return sorted(s) == sorted(t)


Почему O(n log n)?

Потому что sorted() использует сортировку,
а сортировка имеет сложность O(n log n).

Работает нормально…
но можно сделать быстрее.


Оптимальное решение — O(n)
from collections import Counter

def is_anagram(s, t):
return Counter(s) == Counter(t)


🧠 Почему теперь O(n)

Мы просто считаем,
сколько раз встречается каждый символ.

Например:


listen —> {l:1, i:1, s:1, t:1, e:1, n:1}
silent —> {s:1, i:1, l:1, e:1, n:1, t:1}


И сравниваем два словаря.

Один проход по строке —> O(n).

🐍Вопросы с собесов -> ProstoPython
👍3
HTTP/1.1 — самая распространённая версия, долгое время была стандартом интернета.
Особенности:

🔹 постоянные соединения (keep-alive)
🔹 текстовый протокол
🔹 один запрос за раз в одном соединении

HTTP/2 — современная версия, используемая большинством браузеров и серверов.

🔹 бинарный протокол
🔹 мультиплексирование (несколько запросов в одном соединении)
🔹 сжатие заголовков
🔹 уменьшение задержек

HTTP/3 — новейшая версия.

🔹 работает поверх QUIC (UDP)
🔹 быстрее устанавливает соединение
🔹 лучше работает при потере пакетов

🐍Вопросы с собесов -> ProstoPython
👍4
⏱️ Big O Breakdown

Посмотри на код:
def example(arr):
arr.sort()

for i in range(len(arr)):
print(arr[i])


Какая итоговая сложность?

A) O(n)
B) O(n log n)
C) O(n²)
D) O(log n)

Правильный ответ: B) O(n log n)

🧠 Разбор

Здесь две операции:

1️⃣ Сортировка массива

arr.sort()

Сложность сортировки в Python (Timsort):

O(n log n)


2️⃣ Проход по массиву

for i in range(len(arr))

Это:

O(n)


Теперь объединяем:

O(n log n) + O(n)

В Big O оставляем самый медленный рост.

🐍Вопросы с собесов -> ProstoPython
👍4
Rookie Mistakes
Тема: dict.keys() там, где это не нужно


Иногда можно встретить такой код:

Плохо:
d = {"a": 1, "b": 2}

if "a" in d.keys():
print("Found")


Работает? Да.
Но выглядит странно для Python.


Правильно:
if "a" in d:
print("Found")


🧠 Почему так лучше

🔹 in dict по умолчанию проверяет ключи
🔹 код короче и читается быстрее
🔹 не создаётся лишнее представление keys()

🐍Вопросы с собесов -> ProstoPython
👍3🔥1
🧰 Code Cleanup — выпуск 29
Тема: лишняя проверка перед append

Иногда код выглядит так:


Плохо:
result = []

for x in nums:
if x % 2 == 0:
result.append(x)


Работает.
Но в Python есть способ сделать это чище.


Code Cleanup:
result = [x for x in nums if x % 2 == 0]


🧠 Почему так лучше

🔹 короче
🔹 читается быстрее
🔹 сразу видно условие и результат

🐍Вопросы с собесов -> ProstoPython
🔥3
Quick Sort — алгоритм сортировки с разделением массива относительно опорного элемента.

Сложность:

🔹 Средняя — O(n log n)
🔹 Лучшая — O(n log n)
🔹 Худшая — O(n²) (если опорный элемент выбирается неудачно)

Сортировка в Python

В Python (list.sort() и sorted()) используется Timsort.

Особенности:

🔹 гибрид Merge Sort + Insertion Sort
🔹 оптимизирован для частично отсортированных данных
🔹 стабильная сортировка

Сложность:

🔹 O(n log n) в среднем и худшем случае
🔹 O(n) если данные почти отсортированы

Итог:
Quick Sort — O(n log n) в среднем, O(n²) в худшем.
В Python по умолчанию используется Timsort.

🐍Вопросы с собесов -> ProstoPython
👍3
⏱️ Big O Breakdown

Посмотри на код:
def example(n):
i = n
while i > 0:
for j in range(i):
print(j)
i //= 2


Какая сложность?

A) O(n)
B) O(n log n)
C) O(n²)
D) O(log n)

Правильный ответ: A) O(n)

🧠 Разбор

Первая итерация:

n операций

Вторая:

n/2

Третья:

n/4

Дальше:

n/8

И так далее.

Если сложить:

n + n/2 + n/4 + n/8 + ...

Получаем примерно:

2n

📌 Итог

Общее количество операций примерно 2n

А значит итоговая сложность:

O(n)

💡 Ловушка на собеседованиях

Многие видят:

🔹цикл внутри цикла

🔹деление на 2

и автоматически отвечают O(n log n).


🐍Вопросы с собесов -> ProstoPython
👍3
🧰 Code Cleanup — выпуск 30
Тема: лишняя переменная в цикле


Иногда код создаёт переменную, которая используется всего один раз.

Плохо:
squares = []

for x in nums:
square = x * x
squares.append(square)


Переменная square здесь ничего не объясняет и живёт всего одну строку.

Code Cleanup:
squares = []

for x in nums:
squares.append(x * x)


Или ещё чище:
squares = [x * x for x in nums]


🧠 Почему так лучше

🔹 меньше лишних переменных
🔹 код короче
🔹 логика читается быстрее
🔹 меньше «шума»

🐍Вопросы с собесов -> ProstoPython
🔥2👍1
Rookie Mistakes
Тема: путаница между append() и extend()


Иногда можно увидеть такой код:
nums = [1, 2, 3]
nums.append([4, 5])

print(nums)


Многие ожидают получить:
[1, 2, 3, 4, 5]

Но реальный вывод будет:
[1, 2, 3, [4, 5]]


🧠 В чём ошибка


Метод append() добавляет объект целиком.

То есть список [4, 5] добавляется как один элемент.

Если нужно добавить элементы списка

Используй extend():
nums = [1, 2, 3]
nums.extend([4, 5])

print(nums)


Теперь результат будет:
[1, 2, 3, 4, 5]


🐍Вопросы с собесов -> ProstoPython
👍3
⏱️ Big O Breakdown

Посмотри на код:
def example(n):
for i in range(n):
for j in range(n):
for k in range(n):
print(i, j, k)


Какая сложность?

A) O(n)
B) O(n²)
C) O(n³)
D) O(n log n)

Правильный ответ: C) O(n³)

🧠 Разбор

Первый цикл:

n

Второй цикл:

n

Третий цикл:

n

Общее количество операций:

n × n × n =

📌 Итог

Сложность алгоритма:

O(n³)

🐍Вопросы с собесов -> ProstoPython
👍4
📈 From O(n²) to O(n)

Задача: найти максимальную сумму подмассива.
(классическая задача, которую любят на собеседованиях)


Например:


nums = [-2,1,-3,4,-1,2,1,-5,4]

Ответ:

6

Потому что лучший подмассив:

[4, -1, 2, 1]

Наивное решение — O(n²)
def max_subarray(nums):
max_sum = float('-inf')

for i in range(len(nums)):
current = 0
for j in range(i, len(nums)):
current += nums[j]
max_sum = max(max_sum, current)

return max_sum


Что происходит:

🔷выбираем начало подмассива

🔷перебираем все возможные продолжения

🔷Два цикла —> O(n²).

Оптимизация — O(n)
Алгоритм Кадане.

def max_subarray(nums):
current = max_sum = nums[0]

for num in nums[1:]:
current = max(num, current + num)
max_sum = max(max_sum, current)

return max_sum


🧠 Идея

Если текущая сумма становится хуже, чем просто новый элемент — начинаем новый подмассив.

То есть на каждом шаге решаем:

🔷продолжать текущий массив
🔷или начать новый

🐍Вопросы с собесов -> ProstoPython
🔥4
В Django есть 3 типа наследования моделей

Abstract Base Class

🔹 Базовая модель не создаёт таблицу
🔹 Поля наследуются в дочерние модели

Multi-table inheritance

🔹 Для каждой модели создаётся отдельная таблица
🔹 Между ними связь OneToOne

Proxy model

🔹 Не создаёт новую таблицу
🔹 Меняет поведение (например, менеджеры, методы)

Итог

🔹 Abstract — переиспользование полей без таблицы
🔹 Multi-table — расширение с отдельной таблицей
🔹 Proxy — изменение поведения без изменения структуры

🐍Вопросы с собесов -> ProstoPython
👍4
🧰 Code Cleanup — выпуск 31
Тема: лишний range(len(...))


Очень частая конструкция у новичков:

Плохо:
for i in range(len(nums)):
print(nums[i])


Работает? Да.
Но это не самый читаемый вариант.

Code Cleanup:
for num in nums:
print(num)


🧠 Почему так лучше

🔹 не нужен индекс, если ты его не используешь
🔹 код короче и чище
🔹 сразу понятно, что мы итерируемся по элементам

🐍Вопросы с собесов -> ProstoPython
👍4
Попробуйте ответить без запуска, что выведет код, разбор будет через 2 часа

🐍Вопросы с собесов -> ProstoPython
🔥3
Что выведет код выше?
Anonymous Poll
43%
[2, 2, 2]
21%
[0, 1, 2][0, 0, 0]
36%
Ошибка
🔥1
Ответ: [2, 2, 2]

Lambda не захватывает значение i в момент создания. Она захватывает переменную i из замыкания.

К моменту вызова f() цикл уже завершился, и i == 2. Все три функции смотрят на одну и ту же переменную.

# Фикс через default-аргумент:

funcs = []

for i in range(3):
funcs.append(lambda x=i: x)

print([f() for f in funcs]) # [0, 1, 2]


Default-аргумент вычисляется сразу при создании функции — поэтому каждый раз фиксируется текущее значение i.


Замыкание в Python хранит ссылку на переменную из enclosing scope, а не её копию. Это нужно чувствовать на уровне рефлексов.

🐍Вопросы с собесов -> ProstoPython
👍4
📈 FROM O(...) TO O(...)
Поиск дубликатов: от O(n²) до O(n)

Задача простая: есть ли в списке повторяющиеся элементы? Почти все пишут первое, что приходит в голову. И почти всегда это медленно.

Наивное решение:

def has_duplicates(nums):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j]:
return True
return False


⏱️ O(n²) время / O(1) память

Два вложенных цикла. На 10 000 элементов — 50 миллионов сравнений.

Оптимизированное решение:

def has_duplicates(nums):
seen = set()
for n in nums:
if n in seen:
return True
seen.add(n)
return False


⚡️ O(n) время / O(n) память

🐍Вопросы с собесов -> ProstoPython
👍4
🧩 Code Cleanup

Декораторы: не пиши один и тот же код дважды

Часто вижу такое:

def get_user(user_id):
print(f"Вызов get_user с {user_id}")
result = db.query(user_id)
print(f"get_user выполнился за ...")
return result

def get_orders(user_id):
print(f"Вызов get_orders с {user_id}")
result = db.query_orders(user_id)
print(f"get_orders выполнился за ...")
return result


Логирование копируется в каждую функцию. Если нужно что-то поменять — правишь везде.


После:

import functools
import time

def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__} с {args} {kwargs}")
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} выполнился за {time.perf_counter() - start:.4f}с")
return result
return wrapper

@log
def get_user(user_id):
return db.query(user_id)

@log
def get_orders(user_id):
return db.query_orders(user_id)


🐍Вопросы с собесов -> ProstoPython
👍4
⏱️ Big O Breakdown

`sorted()` vs `list.sort()`: в чём разница по памяти

Оба сортируют. Оба используют Timsort. Какова сложность каждого?

nums = [3, 1, 4, 1, 5, 9, 2, 6]

result = sorted(nums)
nums.sort()


Варианты:
А) Оба O(n log n) время, O(1) память
Б) sorted — O(n log n) / O(n), list.sort() — O(n log n) / O(1)
В) Оба O(n log n) время, O(n) память
Г) sorted — O(n), list.sort() — O(n log n)

Ответ: Б

Алгоритм один и тот же — Timsort, O(n log n) в среднем и худшем случае. Разница в памяти.

list.sort() сортирует список на месте. Дополнительная память — O(1), исходный список меняется.

sorted() создаёт новый список. Дополнительная память — O(n), исходный остаётся нетронутым.

nums = [3, 1, 4, 1, 5]

result = sorted(nums)
print(nums) # [3, 1, 4, 1, 5] — не изменился
print(result) # [1, 1, 3, 4, 5] — новый объект

nums.sort()
print(nums) # [1, 1, 3, 4, 5] — изменился на месте


Ловушка, которую все допускают:

def process(data):
data.sort() # мутирует оригинал
...

my_list = [3, 1, 2]
process(my_list)
print(my_list) # [1, 2, 3] — сюрприз


Передал список в функцию, а он изменился снаружи. Если это не задумано — баг.

Когда что использовать:

list.sort() — когда исходный порядок не нужен и важна память. sorted() — когда нужно сохранить оригинал или сортируешь не список (tuple, generator, любой iterable).

Одинаковая нотация O(n log n) не означает одинаковое поведение. Разница в мутабельности и памяти — это детали, которые в продакшене стоят дорого.


🐍Вопросы с собесов -> ProstoPython
🔥4