Эта статья относится к серии статей «Покоряя LeetCode», официально стартовавшей 12.08.2021. Поскольку некоторые вопросы в LeetCode заблокированы, эта серия будет продолжаться, по крайней мере, до тех пор, пока все разблокированные вопросы не будут удалены; поскольку LeetCode все еще создает новые вопросы, дата окончания этой серии может быть навсегда. В этой серии статей о решении проблем я не только объясню различные идеи решения проблем и их оптимизацию, но также буду использовать различные языки программирования для решения проблем.Что касается общих методов решения, я буду также суммируйте соответствующие шаблоны алгоритмов.
Чтобы облегчить запуск, отладку и обмен файлами кода на ПК, я также создал соответствующие хранилища . На этом складе вы можете увидеть не только ссылку на исходный вопрос LeetCode, код решения, ссылку на статью решения, сводку похожих вопросов, сводку распространенных решений и т. д., но также важную информацию, такую как частота исходных вопросов. и связанных с ними компаний. Если у вас есть другие предпочтительные решения, вы можете поделиться ими с другими.
Поскольку содержание этой серии статей может быть обновлено и изменено в любое время, вы можете подписаться на статью «Оглавление серии статей Conquering LeetCode» и сохранить ее в качестве напоминания.
Реализуйте MyCalendar
класс для хранения вашего расписания. Если расписание, которое вы хотите добавить, не приводит к двойному бронированию , вы можете сохранить новое расписание.
Двойное резервирование происходит, когда два расписания частично перекрываются во времени (например, оба расписания относятся к одному и тому же периоду времени) .
Расписание может быть представлено парой целых чисел start
, а end
время здесь представляет собой полуоткрытый интервал, то есть [start, end)
диапазон действительных чисел x
равен start <= x < end
.
MyCalendar
Класс реализации :
MyCalendar()
Инициализируйте объект календаря.boolean book(int start, int end)
Вернитесь, если расписание можно успешно добавить в календарь, не вызывая двойного бронированияtrue
. В противном случае вернитесь назадfalse
и не добавляйте расписание в свой календарь.
Пример:
输入:
["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
输出:
[null, true, false, true]
объяснять:
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。
myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。
намекать:
0 <= start < end <= 10^9
- Каждый тестовый пример может вызывать
book
метод не более1000
одного раза.
Этот вопрос взят из: Еженедельного конкурса 59. Подобные вопросы, касающиеся интервальных моделей, включают:
- 715. Модуль дальности
- 2276. Количество целых чисел в статистическом интервале
- 731. Мой календарь II
- 732. Мой календарь III.
Решение 1. Траверс напрямую
Мы записываем все забронированные интервалы расписания курса, а когда бронируем новый интервал [start, end) [\textit{start}, \textit{end})[ начало ,end ) , в это время проверьте, не конфликтует ли каждое забронированное в данный момент расписание с новым расписанием. Если конфликта нет, можно добавить новое расписание.
- Для двух интервалов [s 1 , e 1 ) [s_1, e_1)[ с1,е1) сумма[ s 2 , e 2 ) [s_2, e_2)[ с2,е2) , если между ними нет пересечения, тоs 1 ≥ e 2 s_1 \ge e_2с1≥е2s 2 ≥ e 1 s_2 \ge e_1с2≥е1, что означает, что если s 1 < e 2 s_1 < e_2с1<е2и s 2 < e 1 s_2 < e_1с2<е1
。
class MyCalendar {
private:
vector<pair<int, int>> booked;
public:
bool book(int start, int end) {
for (auto &[l, r] : booked)
if (l < end && start < r) return false;
booked.emplace_back(start, end);
return true;
}
};
Анализ сложности:
- Временная сложность: O ( n 2 ) O(n^2)О ( н2 ), гдеnnn представляет количество расписаний. Потому что каждый раз, когда вы бронируете билет, вам необходимо просмотреть все забронированные маршруты.
- Пространственная сложность: O ( n ) O(n)O ( n ) , из нихnnn представляет количество расписаний. Все забронированные поездки необходимо сохранять.
Решение 2. Бинарный поиск
Если мы поддерживаем расписания в хронологическом порядке, мы можем проверить, может ли новое расписание быть забронировано с помощью двоичного поиска ситуации с расписанием , и если да, то обновить вставленное расписание в структуру сортировки.
Если вам нужна структура данных, которая может сортировать элементы и поддерживать быструю вставку , вы можете использовать TreeSet \texttt{TreeSet}TreeSet для сборки. Для заданного интервала[начало, конец) [начало,конец)[ начало , _ _ _ _e n d ) каждый раз, когда мы обнаруживаем, что начальная точка больше или равнаend \textit{end}Первый интервал конца [l 1, r 1) [l_1,r_1)[ л1,р1) , а рядом с[ l 1 , r 1 ) [l_1,r_1)[ л1,р1) есть[ l 2 , r 2 ) [l_2,r_2)[ л2,р2) , еслиr 2 ≤ start < end ≤ l 1 r_2 \le \textit{start} < \textit{end} \le l_1р2≤начинать<конец≤л1, то интервал можно забронировать.
class MyCalendar {
set<pair<int, int>> booked;
public:
bool book(int start, int end) {
auto it = booked.lower_bound({
end, 0});
if (it == booked.begin() || (--it)->second <= start) {
booked.emplace(start, end);
return true;
}
return false;
}
};
Анализ сложности:
- Временная сложность: O ( n log n ) O(n\log n)О ( нло гn ) , гдеnnn представляет количество расписаний. Поскольку каждый раз при резервировании требуется бинарный поиск, а требуемое время равноO ( log n ) O(\log n)О ( log гп )。
- Пространственная сложность: O ( n ) O(n)O ( n ) , из нихnnn представляет количество расписаний. Все забронированные поездки необходимо сохранять.
Решение 3. Дерево отрезков линии
Предположим, используя дерево отрезков, мы открываем массив arr [ 0 , ⋯ , 1 0 9 ] \textit{arr}[0,\cdots, 10^9]обр [ 0 ,⋯,1 09 ]первоначально значение каждого элемента равно0 00 , для каждого интервала бронирования поездки[начало, конец) [начало, конец)[ начало , _ _ _ _e n d ) , затем меняем элементы в интервалеarr [ start , ⋯ , end − 1 ] \textit{arr}[\textit{start},\cdots,\textit{end}-1]прибытие [ начало ,⋯,конец−1 ] каждый элемент помечен1 11 , каждый раз, когдакнига \texttt{book}book , нам нужно только обнаружитьarr [ start , ⋯ , end − 1 ] \textit{arr}[\textit{start},\cdots,\textit{end}-1]прибытие [ начало ,⋯,конец−1 ] Есть ли какой-либо элемент в интервале, отмеченном как1 11 . На самом деле нам не обязательно открывать массивarr \textit{arr}arr , можно использовать динамическое дерево отрезков линий, lazy marklazy \textit{lazy}интервал ленивой отметки [ l , r ] [l,r][ л ,r ] уже забронировано,дерево \textit{tree}интервал записи дерева [l, r] [l,r][ л ,Существование r ] отмечается как 1 11 элемент.
Каждый раз книга \texttt{book}При работе с книгой сначала определите интервал [start, ⋯, end − 1] [\textit{start},\cdots,\textit{end}-1][ начало ,⋯,конец−1 ] Есть ли элемент, который нужно отметить, если он существует, он помечен как1 1Элемент 1 указывает, что интервал не может быть забронирован; в противном случае его можно зарезервировать. После завершения резервированияarr [ start , ⋯ , end − 1 ] \textit{arr}[\textit{start},\cdots,\textit{end}-1]прибытие [ начало ,⋯,конец−1 ] отмечен как1 11 и одновременно обновите дерево сегментов.
class MyCalendar {
unordered_set<int> tree, lazy;
public:
bool query(int start, int end, int l, int r, int idx) {
if (r < start || end < l) {
return false;
}
/* 如果该区间已被预订,则直接返回 */
if (lazy.count(idx)) {
return true;
}
if (start <= l && r <= end) {
return tree.count(idx);
}
int mid = (l + r) >> 1;
return query(start, end, l, mid, 2 * idx) ||
query(start, end, mid + 1, r, 2 * idx + 1);
}
void update(int start, int end, int l, int r, int idx) {
if (r < start || end < l) {
return;
}
if (start <= l && r <= end) {
tree.emplace(idx);
lazy.emplace(idx);
} else {
int mid = (l + r) >> 1;
update(start, end, l, mid, 2 * idx);
update(start, end, mid + 1, r, 2 * idx + 1);
tree.emplace(idx);
if (lazy.count(2 * idx) && lazy.count(2 * idx + 1)) {
lazy.emplace(idx);
}
}
}
bool book(int start, int end) {
if (query(start, end - 1, 0, 1e9, 1)) {
return false;
}
update(start, end - 1, 0, 1e9, 1);
return true;
}
};
или:
class MyCalendarTwo {
public:
MyCalendarTwo() {
}
void update(int s, int e, int val, int l, int r, int idx) {
if (r < s || l > e) return;
if (s <= l && r <= e) {
tree[idx].first += val;
tree[idx].second += val;
} else {
int mid = (l + r) >> 1;
update(s, e, val, l, mid, 2 * idx);
update(s, e, val, mid + 1, r, 2 * idx + 1);
tree[idx].first = tree[idx].second + max(tree[2 * idx].first, tree[2 * idx + 1].first);
}
}
bool book(int start, int end) {
update(start, end - 1, 1, 0, 1e9, 1);
if (tree[1].first > 2) {
update(start, end - 1, -1, 0, 1e9, 1);
return false;
}
return true;
}
private:
unordered_map<int, pair<int, int>> tree;
};
Анализ сложности:
- Временная сложность: O ( n log C ) O(n \ log C)О ( нло гC ) , гдеnnn — количество расписаний. Благодаря использованию запросов к дереву сегментов максимальная глубина дерева сегментов равнаlog C \log C.ло гC , не более log C \log Cбудет запрашиваться каждый разло гИмеется C узлов, и временная сложность, необходимая для каждого поиска, равнаO ( log C + log C ) O(\log C + \log C)О ( log гС+ло гC ) , поэтому временная сложность равнаO ( n log C ) O(n \ log C)О ( нло гC ) , здесьCCC принимает фиксированное значение, равное1 0 9 10^9.1 09
- Пространственная сложность: O ( n log C ) O(n \ log C)О ( нло гC ) , гдеnnn — количество расписаний. Поскольку в этом решении используется динамическое дерево сегментов, максимальная глубина дерева сегментов равнаlog C \log C.ло гC каждое резервирование добавит не более log C \log Cк дереву сегментов.ло гC узлов, поэтому пространственная сложность равнаO ( n log C ) O(n \ log C)О ( нло гC ) , здесьCCC принимает фиксированное значение, равное1 0 9 10^9.1 09