#python #algorithm #linked-list
Вопрос:
Я выполнял задачу «Удалить цикл в связанном списке» для вундеркиндов для вундеркиндов:
Задан связанный список из N узлов, такой, что он может содержать цикл.
Цикл здесь означает, что последний узел списка ссылок подключен к узлу в позиции X. Если в списке ссылок нет цикла, X=0.
Удалите цикл из связанного списка, если он присутствует.
Я попробовал использовать этот код:
class Solution:
def removeLoop(self, q):
slow,prev = q,None
fast = q
while slow and fast and fast.next:
prev = fast
slow = slow.next
fast = fast.next.next
if fast == slow:
print(prev.data)
prev.next = None
return
Но это работает неправильно не во всех случаях.
Например, он удаляет неправильную ссылку, когда это список входных данных:
1 → 2 → 3
↑ ↓
└── 4
В этом случае он удаляет ссылку со 2 по 3, в то время как он должен удалить ссылку с 4 по 2.
Вот код для воспроизведения проблемы:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Solution:
def removeLoop(self, q):
slow,prev = q,None
fast = q
while slow and fast and fast.next:
prev = fast
slow = slow.next
fast = fast.next.next
if fast == slow:
print(prev.data) # Outputs 2, but should output 4
prev.next = None
return
# Create list 1 -> 2 -> 3 -> 4
one = Node(1)
two = Node(2)
three = Node(3)
four = Node(4)
one.next = two
two.next = three
three.next = four
# Add a link from last node to second node
four.next = two # cycle
Solution().removeLoop(one)
Я не знаю, где я ошибся?
Комментарии:
1. используйте
print()
, чтобы увидеть, какая часть кода выполняется и что у вас есть в переменных — и сравните это с «вычислениями» на бумаге. Используйте небольшие примеры данных, чтобы сделать это проще. Это может помочь вам понять, в чем проблема.
Ответ №1:
Ваш алгоритм неверен. Он правильно определяет цикл-используя алгоритм обнаружения циклов Флойда, но место, где slow
они fast
встречаются, не обязательно является местом, где нужно удалить ссылку.
В качестве примечания: я бы не стал называть данный аргумент q
… Обычно называют ссылку на первый узел списка head
.
Мы можем найти первый узел цикла , сбросив одну из двух ссылок (например fast
) на head
, а затем позволить обеим ссылкам двигаться с одинаковой скоростью, пока они снова не встретятся. Затем вы нашли первый узел, который является частью цикла.
Поскольку нам действительно нужен последний узел цикла (чтобы мы могли установить его next
атрибут None
), нам нужно разорвать этот второй цикл на один шаг раньше:
def removeLoop(self, head):
slow = head
fast = slow
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast == slow:
# Detected the cycle, but still need to find last node:
fast = head
while fast.next != slow.next:
fast = fast.next
slow = slow.next
slow.next = None
return
Однако, если цикл включает головной узел, то мы уже слишком поздно выходим из этого цикла «на один шаг раньше». Вот почему я бы предложил добавить фиктивный узел в список и сделать его временным главой. Таким образом, мы уверены, что этот головной узел не является частью цикла, и мы не будем «слишком поздно».:
def removeLoop(self, head):
slow = Node(None) # A dummy node
slow.next = head # Prepend it to the list
head = fast = slow # ...and start there
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast == slow:
fast = head
while fast.next != slow.next:
fast = fast.next
slow = slow.next
slow.next = None
return