# 🧮 Алгоритмическая задача для Middle разработчика ## 📝 Задача: Планировщик встреч ### **Условие:** У вас есть список встреч, каждая встреча представлена как массив `[start, end]`, где `start` и `end` - время начала и окончания в минутах от начала дня. Нужно найти максимальное количество непересекающихся встреч, которые можно провести в одной переговорной комнате. ### **Примеры:** **Пример 1:** ``` Ввод: meetings = [[0,30],[5,10],[15,20]] Вывод: 2 Объяснение: Можно выбрать встречи [5,10] и [15,20] ``` **Пример 2:** ``` Ввод: meetings = [[7,10],[2,4]] Вывод: 2 Объяснение: Обе встречи не пересекаются ``` **Пример 3:** ``` Ввод: meetings = [[1,5],[8,9],[8,9],[5,9],[9,15]] Вывод: 3 Объяснение: Можно выбрать [1,5], [8,9], [9,15] ``` ### **Ограничения:** - `1 <= meetings.length <= 10^4` - `meetings[i].length == 2` - `0 <= starti < endi <= 10^6` ### **Задание:** 1. Реализуйте метод `public int maxMeetings(int[][] meetings)` 2. Объясните алгоритм и его сложность 3. Напишите unit-тесты **Время на выполнение:** 15 минут --- # ✅ Решение алгоритмической задачи
Показать решение ### **Основная идея:** Это классическая задача о максимальном количестве непересекающихся интервалов. Используем жадный алгоритм: 1. Сортируем встречи по времени окончания 2. Выбираем встречу с самым ранним окончанием 3. Исключаем все пересекающиеся с ней встречи 4. Повторяем для оставшихся встреч ### **Решение:** ```java import java.util.*; public class MeetingScheduler { public int maxMeetings(int[][] meetings) { if (meetings == null || meetings.length == 0) { return 0; } // Сортируем по времени окончания Arrays.sort(meetings, (a, b) -> Integer.compare(a[1], b[1])); int count = 0; int lastEndTime = -1; for (int[] meeting : meetings) { int startTime = meeting[0]; int endTime = meeting[1]; // Если текущая встреча не пересекается с предыдущей выбранной if (startTime >= lastEndTime) { count++; lastEndTime = endTime; } } return count; } } ``` ### **Объяснение алгоритма:** 1. **Сортировка:** Сортируем все встречи по времени окончания. Это ключевая идея - выбирая встречи с самым ранним окончанием, мы оставляем максимум времени для последующих встреч. 2. **Жадный выбор:** Проходим по отсортированным встречам и выбираем те, которые не пересекаются с уже выбранными. 3. **Условие непересечения:** Встреча не пересекается с предыдущей, если её время начала >= времени окончания предыдущей. ### **Временная сложность:** O(n log n) - из-за сортировки ### **Пространственная сложность:** O(1) - если не считать пространство для сортировки ### **Пошаговый пример:** Для `meetings = [[0,30],[5,10],[15,20]]`: 1. После сортировки по времени окончания: `[[5,10], [15,20], [0,30]]` 2. Выбираем `[5,10]`, `lastEndTime = 10` 3. Проверяем `[15,20]`: `15 >= 10` ✓, выбираем, `lastEndTime = 20` 4. Проверяем `[0,30]`: `0 < 20` ✗, пропускаем 5. Результат: 2 встречи Для `meetings = [[1,5],[8,9],[8,9],[5,9],[9,15]]`: 1. После сортировки по времени окончания: `[[1,5], [8,9], [8,9], [5,9], [9,15]]` 2. Выбираем `[1,5]`, `lastEndTime = 5` 3. Проверяем `[8,9]`: `8 >= 5` ✓, выбираем, `lastEndTime = 9` 4. Проверяем `[8,9]`: `8 < 9` ✗, пропускаем 5. Проверяем `[5,9]`: `5 < 9` ✗, пропускаем 6. Проверяем `[9,15]`: `9 >= 9` ✓, выбираем 7. Результат: 3 встречи ### **Unit тесты:** ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MeetingSchedulerTest { private final MeetingScheduler scheduler = new MeetingScheduler(); @Test void testExample1() { int[][] meetings = {{0,30},{5,10},{15,20}}; assertEquals(2, scheduler.maxMeetings(meetings)); } @Test void testExample2() { int[][] meetings = {{7,10},{2,4}}; assertEquals(2, scheduler.maxMeetings(meetings)); } @Test void testExample3() { int[][] meetings = {{1,5},{8,9},{8,9},{5,9},{9,15}}; assertEquals(3, scheduler.maxMeetings(meetings)); } @Test void testEmptyInput() { int[][] meetings = {}; assertEquals(0, scheduler.maxMeetings(meetings)); } @Test void testNullInput() { assertEquals(0, scheduler.maxMeetings(null)); } @Test void testSingleMeeting() { int[][] meetings = {{1,3}}; assertEquals(1, scheduler.maxMeetings(meetings)); } @Test void testAllOverlapping() { int[][] meetings = {{1,4},{2,5},{3,6}}; assertEquals(1, scheduler.maxMeetings(meetings)); } @Test void testNoOverlapping() { int[][] meetings = {{1,2},{3,4},{5,6}}; assertEquals(3, scheduler.maxMeetings(meetings)); } @Test void testTouchingMeetings() { int[][] meetings = {{1,3},{3,5},{5,7}}; assertEquals(3, scheduler.maxMeetings(meetings)); } } ``` ### **Почему жадный алгоритм работает:** Выбирая встречу с самым ранним окончанием, мы всегда оставляем максимально возможное время для размещения остальных встреч. Это оптимальная стратегия для данной задачи. **Доказательство корректности:** Пусть OPT - оптимальное решение, а GREEDY - решение жадного алгоритма. Можно показать, что GREEDY не хуже OPT путем замены встреч в OPT на встречи из GREEDY без ухудшения результата.