Files
second-mind-aep/💼 Работа/Собеседования/Лето 2025/Задачи/🧮 Алгоритмическая задача для Middle разработчика.md
2025-08-13 14:53:57 +04:00

204 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🧮 Алгоритмическая задача для 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 минут
---
# ✅ Решение алгоритмической задачи
<details> <summary>Показать решение</summary>
### **Основная идея:**
Это классическая задача о максимальном количестве непересекающихся интервалов. Используем жадный алгоритм:
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 без ухудшения результата.
</details>