Решение задачи с собеседования Middle of the Linked List [+ ВИДЕО]
На видео более подробное объяснение каждого решения
Постановка задачи
Ссылка на задачу: https://leetcode.com/problems/middle-of-the-linked-list
Дан указатель head
на начало односвязного списка, нужно вернуть средний узел списка.
Если средних узлов два, нужно вернуть второй средний узел.
1. Решение с использованием массива
Подход
Преобразование списка в массив упрощает доступ к элементам по индексу. Мы проходим по списку и сохраняем все узлы в массив, так как доступ по индексу в массиве осуществляется за постоянное время O(1)
. После этого мы легко можем найти средний элемент, просто взяв элемент по индексу len(items) // 2
Алгоритм
Пройти по списку и сохранить все его элементы в массив.
Определить индекс среднего элемента массива.
Вернуть элемент, находящийся по этому индексу.
Код решения
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
items = []
while head: #None
items.append(head)
head = head.next
return items[len(items) // 2]
Сложность алгоритма
• По времени: O (n), где n — количество элементов в списке.
• По памяти: O (n), так как мы сохраняем все элементы списка в массив.
Визуализация
Исходный список:
1 -> 2 -> 3 -> 4 -> 5
Массив после прохождения по списку:
[1, 2, 3, 4, 5]
Индекс среднего элемента:
5 // 2 = 2
Средний элемент в массиве: 3
0 1 2 3 4
[1, 2, {3}, 4, 5]
2. Решение с подсчетом длины списка
Подход
В этом подходе мы сначала считаем количество элементов в списке, чтобы точно знать, сколько узлов в нем содержится. Считая количество узлов, мы можем определить индекс среднего элемента как length // 2
. Затем, проходя список во второй раз, мы останавливаемся на этом индексе и возвращаем соответствующий узел.
Алгоритм
Пройти по списку и подсчитать количество элементов.
Пройти по списку снова до среднего элемента и вернуть его.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
count = self.count(head)
for _ in range(count // 2):
head = head.next
return head
def count(self, head):
res = 0
while head:
res += 1
head = head.next
return res
Сложность алгоритма
• По времени: O (n), где n — количество элементов в списке.
• По памяти: O (1), так как мы не используем дополнительную память.
Визуализация
Исходный список:
1 -> 2 -> 3 -> 4 -> 5
Проход 1: Подсчет длины списка
• Длина списка:
5
Средний индекс:
5 // 2 = 2
Проход 2: Получение среднего элемента
• Проходим через узлы до индекса, который мы вычислили на 3 шаге:
1 (0), 2 (1), 3 (2)
Средний элемент: 3
3. Решение с использованием метода быстрого и медленного указателя
Подход
Этот метод использует два указателя: быстрый и медленный. Быстрый указатель движется по два узла за раз, а медленный — по одному. Когда быстрый указатель достигает конца списка или выходыт за пределы списка, медленный находится на середине. Это происходит потому, что быстрый указатель проходит вдвое больше узлов за одно и то же время. Этот метод эффективен, так как требует только одного прохода по списку и использует минимальное дополнительное пространство.
Алгоритм
Инициализировать два указателя, быстрый и медленный, которые указывают на начало списка.
Перемещать медленный указатель на один шаг, а быстрый на два шага, пока быстрый указатель не достигнет конца списка.
Когда быстрый указатель достигнет конца списка или выходит за его пределы, медленный указатель будет находиться на среднем элементе.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
Сложность алгоритма
• По времени: O (n), где n — количество элементов в списке.
• По памяти: O (1), так как используем только два указателя.
Визуализация
Исходный список:
1 -> 2 -> 3 -> 4 -> 5
Начальное положение:
• Медленный указатель (S) на 1
• Быстрый указатель (F) на 1
Шаг 1:
•
S: 2
(двигается на 1 узел вперед)•
F: 3
(двигается на 2 узла вперед)• Список:
1 -> [2 (S)] -> 3 -> [4 (F)] -> 5
Шаг 2:
•
S: 3
(двигается на 1 узел вперед)•
F: 5
(двигается на 2 узла вперед)• Список:
1 -> 2 -> [3 (S)] -> 4 -> [5 (F)]
Конец:
• Быстрый указатель (F) достигает конца списка или выходит за его пределы.
• Медленный указатель (S) находится на 3, который и является средним элементом.
Итоговый результат: Средний элемент списка 3.