Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions conditions/lesson04/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Задача 1: Ты в прогрессируешь?
**Условие:**
Дан список целых чисел, который может быть не отсортирован. Требуется определить, можно ли расположить элементы этого списка в таком порядке, чтобы они образовывали арифметическую прогрессию. Арифметическая прогрессия — это последовательность чисел, в которой разность между любыми двумя соседними элементами постоянна. Например, последовательности `[1, 3, 5, 7]` и `[10, 5, 0, -5]` являются арифметическими прогрессиями, а `[1, 2, 4]` — нет.

Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task1.py).

**Входные данные:**
- `lst` — список целых чисел:
- Длина списка от 0 до 1000;
- Значения элементов от -10⁶ до 10⁶;
- Список может содержать дубликаты и быть неотсортированным.

**Выходные данные:**
- `True`, если элементы списка являются элементами арифметической прогрессии, иначе `False`.

**Примеры:**

| Входные данные | Выходные данные |
|------------------|------------------|
| `[3, 1, 5, 7]` | `True` |
| `[1, 2, 4]` | `False` |

# Задача 2: Почему в сутках не 25 часов?
**Условие:**
Первокурсник Азат пытался выделить время на подготовку к трём (на самом деле — семи) дедлайнам на этой неделе. Он записал все временные интервалы, когда *теоретически* мог бы учиться: например, `[9, 12]` — «проснулся и сразу в бой», `[11, 14]` — «решил продолжить, пока не ушёл обедать». Но оказалось, что некоторые интервалы пересекаются или даже сливаются в один непрерывный марафон отчаяния.

Теперь Азату нужно понять, сколько **реальных блоков времени** у него есть — то есть объединить все пересекающиеся или касающиеся интервалы в непрерывные отрезки. Помогите ему не запутаться в собственном расписании!

Дан список отрезков на числовой прямой. Каждый отрезок задаётся парой целых чисел — его левой и правой границей (включительно). Требуется объединить все пересекающиеся или касающиеся отрезки и вернуть список непересекающихся отрезков, покрывающих ту же область.

Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task2.py).

**Входные данные:**
- `intervals` — список списков целых чисел:
- Каждый вложенный список содержит ровно два элемента: `[left, right]`;
- Гарантируется, что `left <= right` для каждого отрезка;
- Длина списка от 0 до 1000;
- Значения границ: `0 ≤ left, right ≤ 10⁶`.

**Выходные данные:**
- Список списков целых чисел:
- Каждый элемент — объединённый отрезок вида `[left, right]`;
- Отрезки в результате не пересекаются и не касаются друг друга;
- Список отсортирован по возрастанию левых границ.

**Примеры:**

| Входные данные | Выходные данные |
|-------------------------------------------------|-------------------------------------|
| `[[10, 13], [1, 3], [2, 6], [8, 10], [15, 18]]` | `[[1, 6], [8, 13], [15, 18]]` |
| `[[1, 4], [4, 5]]` | `[[1, 5]]` |


# Задача 3: Особенный
**Условие:**
Дан массив целых чисел, в котором все числа ведут себя прилично — встречаются ровно два раза, как положено в обществе. Но одно число возомнило себя «уникальным снежинкой» и появилось всего один раз, будто оно может решать что-то.

Ваша задача — найти этого самопровозглашённого индивидуалиста, используя минимально возможную память и ровно один проход по данным. Решение должно работать за O(N) по времени и O(1) по памяти — потому что даже уникальность не отменяет законы эффективности.

Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task3.py).

**Входные данные:**
- `nums` — список целых чисел:
- Длина списка: нечётная от 1 до 1001;
- Все числа, кроме одного, встречаются ровно два раза;
- Значения элементов от -10⁶ до 10⁶`.

**Выходные данные:**
- Целое число, которое встречается в списке один раз.

**Примеры:**

| Входные данные | Выходные данные |
|----------------------|-----------------|
| `[2, 2, 1]` | `1` |
| `[4, 1, 2, 1, 2]` | `4` |


# Задача 4: Очистка от мусора
**Условие:**
Вы работаете с «грязными» данными: в списке чисел случайным образом оказались нули — будто кто-то нажал «сброс» в самый неподходящий момент. Чтобы система не сломалась, нужно аккуратно убрать все нули в конец, **не меняя порядок остальных чисел** и **не создавая новый список** (память на сервере — дороже кофе в пятницу).

После перестановки вам нужно сообщить, **с какого индекса начинается «нулевая зона»** — это поможет другим модулям знать, где заканчиваются полезные данные.

Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task4.py).

**Входные данные:**
- `nums` — список целых чисел:
- Длина от 0 до 1000;
- Значения элементов от -10⁶ до 10⁶.

**Выходные данные:**
- Функция **изменяет исходный список на месте**;
- Возвращает **целое число** — индекс первого нуля после перестановки;
- Если нулей нет, возвращается длину `nums`;
- Если все элементы — нули, возвращается `0`.

**Примеры:**

| Входные данные | Список после обработки | Выходные данные |
|----------------------|------------------------|-----------------|
| `[0, 1, 0, 3, 12]` | `[1, 3, 12, 0, 0]` | `3` |
| `[1, 0, 2, 0, 3, 0]` | `[1, 2, 3, 0, 0, 0]` | `3` |


# Задача 5: Трудяга
**Условие:**
Вы анализируете логи работы серверов в дата-центре. Каждый сервер представлен строкой из нулей и единиц:
- **0** означает «простаивал»,
- **1** — «обрабатывал запросы».

Из-за особенностей системы, в каждой строке сначала идут **нули**, а затем — **сплошной блок единиц** (без разрывов). Ваша задача — найти **сервер, который проработал дольше всех** (то есть строку с наибольшим количеством единиц).

И чтобы не тратить драгоценное время на перебор всей матрицы, нужно сделать это **максимально эффективно** — за **O(N + M)**, где `N` — число серверов, `M` — число временных интервалов.

Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task5.py).

**Входные данные:**
- `matrix` — список списков целых чисел:
- Все вложенные списки имеют одинаковую длину `M`;
- Элементы — только `0` или `1`;
- В каждой строке: сначала идут нули (возможно, 0 штук), затем — единицы (возможно, 0 штук), **без чередования**;
- Ограничения: `0 ≤ N ≤ 10000`, `0 ≤ M ≤ 10000`.

**Выходные данные:**
- Индекс строки (целое число от `0` до `N-1`), в которой **наибольшее количество единиц**
- Если таких строк несколько — верните с минимальным индесом;
- Если матрица пуста — верните `0`.

**Примеры:**

| Входные данные | Выходные данные |
|-------------------------------------------------------------|-----------------|
| `[[0, 0, 1, 1], [0, 1, 1, 1], [1, 1, 1, 1], [0, 0, 0, 1]]` | `2` |
| `[[0, 0, 0], [0, 0, 0], [0, 0, 0]]` | `0` |

# Задача 6: Не зацикливайся
**Условие:**
Вы получили странный массив, в котором каждый элемент — это **указатель на следующую позицию**: значение `arr[i]` говорит, куда «прыгать» из индекса `i`. Гарантируется, что **все элементы вовлечены в циклы** — никто не ведёт в никуда, и нет «хвостов», ведущих к циклу. Даже если элемент указывает сам на себя — это тоже цикл (длины 1).

Ваша задача — определить, **сколько всего независимых циклов** спрятано в этом массиве.

Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task6.py).

**Входные данные:**
- `arr` — список целых чисел:
- Длина от 0 до 1000;
- Каждое значение — корректный индекс: `0 ≤ arr[i] < len(arr)`;
- Гарантируется, что каждый элемент принадлежит ровно одному циклу.

**Выходные данные:**
- Целое число — количество циклов в массиве.

**Примеры:**

| Входные данные | Выходные данные |
|--------------------|-----------------|
| `[1, 0]` | `1` |
| `[0, 1, 2]` | `3` |
| `[1, 0, 3, 2]` | `2` |

3 changes: 3 additions & 0 deletions solutions/lesson04/task1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def is_arithmetic_progression(lst: list[list[int]]) -> bool:
# ваш код
return False
3 changes: 3 additions & 0 deletions solutions/lesson04/task2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def merge_intervals(intervals: list[list[int, int]]) -> list[list[int, int]]:
# ваш код
return [[0,0]]
3 changes: 3 additions & 0 deletions solutions/lesson04/task3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def find_single_number(nums: list[int]) -> int:
# ваш код
return 0
3 changes: 3 additions & 0 deletions solutions/lesson04/task4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def move_zeros_to_end(nums: list[int]) -> list[int]:
# ваш код
return 0
3 changes: 3 additions & 0 deletions solutions/lesson04/task5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def find_row_with_most_ones(matrix: list[list[int]]) -> int:
# ваш код
return 0
3 changes: 3 additions & 0 deletions solutions/lesson04/task6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def count_cycles(arr: list[int]) -> int:
# ваш код
return 0
159 changes: 159 additions & 0 deletions tests/test_lesson04_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import pytest
import random

from solutions.lesson04.task1 import is_arithmetic_progression
from solutions.lesson04.task2 import merge_intervals
from solutions.lesson04.task3 import find_single_number
from solutions.lesson04.task4 import move_zeros_to_end
from solutions.lesson04.task5 import find_row_with_most_ones
from solutions.lesson04.task6 import count_cycles

@pytest.mark.parametrize("lst, expected", [
pytest.param([], True, id="empty_list"),
pytest.param([5], True, id="single_element"),
pytest.param([1, 3], True, id="two_elements"),
pytest.param([3, 1], True, id="two_elements_unsorted"),
pytest.param([1, 3, 5, 7], True, id="already_sorted_ap"),
pytest.param([3, 1, 5, 7], True, id="unsorted_ap"),
pytest.param([1, 2, 4], False, id="not_ap"),
pytest.param([10, 5, 0, -5], True, id="negative_difference"),
pytest.param([1, 1, 1, 1], True, id="constant_sequence"),
pytest.param([1, 2, 3, 5], False, id="almost_ap_but_not"),
pytest.param([0, 0, 1], False, id="two_same_one_different"),
pytest.param([10**5 + i*10**2 for i in range(1000)], True, id="long_list_true"),
pytest.param([10**5 + i*10**2 for i in range(999)] + [1], False, id="long_list_false"),
])
def test_is_arithmetic_progression_parametrized(lst, expected):
if len(lst) > 500:
random.shuffle(lst)
assert is_arithmetic_progression(lst) == expected


@pytest.mark.parametrize("intervals, expected", [
pytest.param([], [], id="empty"),
pytest.param([[1, 3]], [[1, 3]], id="single_interval"),
pytest.param([[10, 13], [1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 13], [15, 18]], id="classic_merge"),
pytest.param([[1, 4], [4, 5]], [[1, 5]], id="touching_intervals"),
pytest.param([[1, 4], [2, 3]], [[1, 4]], id="nested_interval"),
pytest.param([[5, 7], [1, 3], [15, 20], [0, 0], [2, 4], [6, 10], [0, 2]], [[0, 4], [5, 10], [15, 20]], id="unsorted_input"),
pytest.param([[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]], id="no_overlap"),
pytest.param([[1, 10], [2, 3], [4, 5], [6, 7]], [[1, 10]], id="all_merged"),
])
def test_merge_intervals(intervals, expected):
assert merge_intervals(intervals) == expected

@pytest.mark.parametrize("nums, expected", [
pytest.param([2, 2, 1], 1, id="simple_case"),
pytest.param([4, 1, 2, 1, 2], 4, id="middle_single"),
pytest.param([1], 1, id="single_element"),
pytest.param([100, 200, 300, 200, 100], 300, id="large_numbers"),
pytest.param([0, 1, 0], 1, id="with_zero"),
pytest.param([7, 8, 9, 8, 7], 9, id="unsorted"),
pytest.param([i + 10**5 for i in range(500)] + [i + 10**5 for i in range(500)] + [69], 69, id="long_list"),
])
def test_find_single_number(nums, expected):
assert find_single_number(nums) == expected

@pytest.mark.parametrize("input_list, expected_list, expected_index", [
pytest.param([0, 1, 0, 3, 12], [1, 3, 12, 0, 0], 3, id="basic"),
pytest.param([0, 0, 1], [1, 0, 0], 1, id="zeros_first"),
pytest.param([1, 2, 3], [1, 2, 3], 3, id="no_zeros"),
pytest.param([0, 0, 0], [0, 0, 0], 0, id="all_zeros"),
pytest.param([1, 0, 2, 0, 3, 0], [1, 2, 3, 0, 0, 0], 3, id="interleaved"),
pytest.param([], [], 0, id="empty"),
pytest.param([0], [0], 0, id="single_zero"),
pytest.param([42], [42], 1, id="single_nonzero"),
])
def test_move_zeros_to_end_parametrized(input_list, expected_list, expected_index):
arr = input_list[:]
result_index = move_zeros_to_end(arr)
assert arr == expected_list
assert result_index == expected_index


@pytest.mark.parametrize("matrix, expected_row", [
pytest.param(
[[0, 0, 1, 1],
[0, 1, 1, 1],
[0, 0, 0, 1],
[1, 1, 1, 1],
[0, 1, 1, 1]],
3,
id="classic"
),
pytest.param(
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
0,
id="all_zeros"
),
pytest.param(
[[1, 1, 1],
[1, 1, 1],
[1, 1, 1]],
0,
id="all_ones_first"
),
pytest.param(
[[0, 1],
[1, 1]],
1,
id="two_rows"
),
pytest.param(
[[0]],
0,
id="single_zero"
),
pytest.param(
[[1]],
0,
id="single_one"
),
pytest.param(
[],
0,
id="empty_matrix"
),
pytest.param(
[[0, 0, 1],
[0, 1, 1],
[0, 1, 1]],
1,
id="tie"
),
])
def test_find_row_with_most_ones(matrix, expected_row):
assert find_row_with_most_ones(matrix) == expected_row


def test_find_row_with_most_ones_big_data():
size = 10000
matrix = [[0]*size for i in range(size)]
matrix[size-1][size-1] = 1

for i in range(50):
assert find_row_with_most_ones(matrix) == 9999

size = 10000
matrix = [[1]*size for i in range(size)]
matrix[0][0] = 0

for i in range(50):
assert find_row_with_most_ones(matrix) == 1


@pytest.mark.parametrize("input_arr, expected", [
pytest.param([0], 1, id="self_loop"),
pytest.param([1, 0], 1, id="two_cycle"),
pytest.param([1, 2, 0], 1, id="three_cycle"),
pytest.param([0, 1, 2], 3, id="three_self_loops"),
pytest.param([1, 0, 3, 2], 2, id="two_2_cycles"),
pytest.param([2, 0, 1, 4, 3], 2, id="mixed_cycles"),
pytest.param([10, 6, 2, 9, 4, 0, 3, 8, 7, 1, 5], 5, id="mixed_cycles"),
pytest.param([], 0, id="empty"),
])
def test_count_cycles(input_arr, expected):
arr = input_arr[:]
assert count_cycles(arr) == expected
Loading