Связные списки
Слияние списков
Слить два отсортированных односвязных списка
Постановка задачи
Описание (LeetCode - 21. Merge Two Sorted Lists)
Даны головы двух отсортированных по возрастанию односвязных списков list1 и
list2.
Нужно объединить два списка в один отсортированный список и вернуть его голову. Узлы результирующего списка должны состоять из узлов исходных списков.
Пример 1:
list1 = [1, 2, 4]
list2 = [1, 3, 4]
Результат:
[1, 1, 2, 3, 4, 4]
Пример 2:
list1 = []
list2 = []
Результат:
[]
Пример 3:
list1 = []
list2 = [0]
Результат:
[0]
Ограничения:
- Количество узлов в обоих списках от
0до50 -100 <= Node.val <= 100- Оба списка отсортированы по неубыванию
Решение
Эта задача проверяет базовую работу со связными списками. У нас уже есть два отсортированных списка, поэтому не нужно ничего сортировать заново. Достаточно идти по обоим спискам и каждый раз выбирать меньший текущий узел.
Идея решения:
Будем держать два указателя: list1 и list2. Они указывают на первые еще не
использованные узлы списков.
Для удобства создадим фиктивный узел dummy. Он не является частью ответа, но
помогает не обрабатывать отдельно случай, когда мы добавляем первый узел.
Указатель tail всегда будет указывать на последний узел уже собранного списка.
На каждом шаге сравниваем list1.val и list2.val:
- Если значение в
list1меньше или равно значению вlist2, добавляем этот узел в результат. - Иначе добавляем узел из
list2. - Сдвигаем указатель того списка, откуда взяли узел.
- Сдвигаем
tailна добавленный узел.
Когда один список закончится, второй можно присоединить целиком. Он уже отсортирован, и все его оставшиеся узлы больше или равны тем, которые мы уже добавили.
В этой конкретной задаче у нас явно указано, что узлы результирующего списка должны состоять из узлов исходных списков. Поэтому мы переиспользуем узлы входных списков и не создаем их копии.
На интервью этого должно быть достаточно, но если это не обговорено в условии задачи, то стоит явно уточнить у интервьюера: нужно ли сохранить оба входных списка без изменений. Если да, то придется копировать узлы.
Основные шаги:
- Создаем
dummyиtail. - Пока оба списка
list1иlist2не пусты, выбираем меньший текущий узел. - Прицепляем выбранный узел к
tail.next. - Сдвигаем указатель выбранного списка и
tail. - Присоединяем остаток непустого списка.
- Возвращаем
dummy.next.
Рассмотрим пример:
list1 = 1 -> 2 -> 4
list2 = 1 -> 3 -> 4
- Инициализация: создаем
dummy,tailуказывает на него. Сравниваем головы списков.
1 <= 1, берем узел изlist1.tailсдвигается на добавленный узел.
2 > 1, берем узел изlist2.
2 < 3, берем узел изlist1.
4 > 3, берем узел изlist2.
4 <= 4, берем узел изlist1. После этогоlist1закончился - прицепляем остатокlist2целиком.
Итог: 1 -> 1 -> 2 -> 3 -> 4 -> 4. Возвращаем dummy.next.
Фиктивный узел dummy нужен только для удобства. Благодаря ему мы всегда
добавляем новый узел одинаково: tail.next = node. В конце возвращаем не
dummy, а dummy.next.
Оценка сложности
Временная сложность
Каждый узел из обоих списков посещается ровно один раз. Если в первом списке n
узлов, а во втором m, временная сложность равна O(n + m).
Пространственная сложность
Мы не создаем новые узлы для ответа, а переиспользуем существующие. Дополнительная
память равна O(1).
Код решения
Приведем код решения.
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
dummy := &ListNode{}
tail := dummy
for list1 != nil && list2 != nil {
if list1.Val <= list2.Val {
tail.Next = list1
list1 = list1.Next
} else {
tail.Next = list2
list2 = list2.Next
}
tail = tail.Next
}
if list1 != nil {
tail.Next = list1
} else {
tail.Next = list2
}
return dummy.Next
}Итоги
Задача Merge Two Sorted Lists - это базовый пример слияния двух отсортированных последовательностей, только вместо индексов мы двигаем указатели по узлам.
- Суть алгоритма: мы сравниваем текущие головы двух списков и каждый раз добавляем меньший узел в конец результата.
- Эффективность:
- Временная сложность:
O(n + m), так как каждый узел обрабатывается один раз. - Пространственная сложность:
O(1), так как мы переиспользуем узлы исходных списков.
- Временная сложность:
- Граничные случаи:
- Оба списка пустые:
dummy.nextостанетсяnull. - Один список пустой: результатом будет второй список.
- Значения равны: можно брать узел из любого списка, порядок все равно останется корректным.
- Оба списка пустые:
- Оптимальное решение: использует уже заданную сортировку и делает один линейный проход без дополнительного списка.