Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 15 additions & 96 deletions datastructures/linked_lists/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
from abc import ABCMeta, abstractmethod

from datastructures.linked_lists.exceptions import EmptyLinkedList
from datastructures.linked_lists.linked_list_utils import (
has_cycle,
detect_node_with_cycle,
cycle_length,
remove_cycle,
)

T = TypeVar("T")

Expand Down Expand Up @@ -379,14 +385,7 @@ def has_cycle(self):
:return: True if there is a cycle, False otherwise
:rtype: bool
"""
fast_pointer = slow_pointer = self.head
while fast_pointer and slow_pointer and fast_pointer.next:
fast_pointer = fast_pointer.next.next
slow_pointer = slow_pointer.next

if slow_pointer == fast_pointer:
return True
return False
return has_cycle(self.head)

def cycle_length(self) -> int:
"""
Expand All @@ -395,105 +394,25 @@ def cycle_length(self) -> int:
Returns:
int: length of the cycle or number of nodes in the cycle
"""
if not self.head:
return 0
slow_pointer = fast_pointer = self.head

while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next

# Cycle detected
if slow_pointer is fast_pointer:
length = 1
# Move slow pointer by one step to start counting
slow_pointer = slow_pointer.next

# Continue moving the slow pointer until it meets the fast pointer again
while slow_pointer != fast_pointer:
length += 1
slow_pointer = slow_pointer.next

return length

return 0
return cycle_length(self.head)

def detect_node_with_cycle(self) -> Optional[Node]:
"""
Detects the node with a cycle and returns it
"""
if not self.has_cycle():
return None
else:
slow_pointer = fast_pointer = self.head

while fast_pointer and slow_pointer and fast_pointer.next:
fast_pointer = fast_pointer.next.next
slow_pointer = slow_pointer.next

if slow_pointer == fast_pointer:
break
else:
return None

while self.head != slow_pointer:
slow_pointer = slow_pointer.next
self.head = self.head.next
return self.head
return detect_node_with_cycle(self.head)

def remove_cycle(self):
def remove_cycle(self) -> Optional[Node]:
"""
Removes cycle if there exists. This will use the same concept as has_cycle method to check if there is a loop
and remove the cycle
if one is found.
1) Detect Loop using Floyd’s Cycle detection algo and get the pointer to a loop node.
2) Count the number of nodes in loop. Let the count be k.
3) Fix one pointer to the head and another to kth node from head.
4) Move both pointers at the same pace, they will meet at loop starting node.
5) Get pointer to the last node of loop and make next of it as NULL.
:return: True if the cycle has been removed, False otherwise
:rtype: bool
Returns:
Node: head node with cycle removed
"""
fast_pointer = slow_pointer = self.head

while fast_pointer and slow_pointer and fast_pointer.next:
fast_pointer = fast_pointer.next.next
slow_pointer = slow_pointer.next

if slow_pointer == fast_pointer:
pointer_1 = pointer_2 = slow_pointer

# Count the number of nodes in loop
k = 1
while pointer_1.next != pointer_2:
pointer_1 = pointer_1.next
k += 1

# Fix one pointer to head
pointer_1 = self.head

# And the other pointer to k nodes after head
pointer_2 = self.head
for _ in range(k):
pointer_2 = pointer_2.next

# Move both pointers at the same place
# they will meet at loop starting node
while pointer_2 != pointer_1:
pointer_1 = pointer_1.next
pointer_2 = pointer_2.next

# Get pointer to the last node
pointer_2 = pointer_2.next
while pointer_2.next != pointer_1:
pointer_2 = pointer_2.next

# Set the next node of the loop ending node
# to fix the loop
pointer_2.next = None
return True
if not self.head or not self.head.next:
return self.head

return False
return remove_cycle(self.head)

@abstractmethod
def alternate_split(self):
Expand Down
119 changes: 118 additions & 1 deletion datastructures/linked_lists/linked_list_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Callable, Any
from datastructures.linked_lists import Node


Expand All @@ -19,3 +19,120 @@ def find_middle_node(head: Optional[Node]) -> Optional[Node]:
fast_pointer = fast_pointer.next.next

return slow_pointer


def has_cycle(
head: Optional[Node], func: Optional[Callable[[Node, Node], Any]] = None
) -> bool:
f"""
Checks if a given linked list has a cycle if a head node is provided. A cycle is when a linked list node can be
reached again after traversing the entire linked list
Args:
head(Node): head node of a linked list
func(Callable): An optional callable function that if passed in will be executed
Returns:
bool: True if there is a cycle, False otherwise
"""
if not head or not head.next:
return False

fast_pointer, slow_pointer = head
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next

if slow_pointer is fast_pointer:
# do something after detecting cycle passing in the two nodes
if func:
func(slow_pointer, fast_pointer)
else:
return True
return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def cycle_length(head: Optional[Node]) -> int:
"""
Determines the length of the cycle in a linked list if it has one. The length of the cycle is the number
of nodes that are 'caught' in the cycle
Args:
head(Node): head node of linked list
Returns:
int: length of the cycle or number of nodes in the cycle
"""
if not head:
return 0
slow_pointer = fast_pointer = head

while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next

# Cycle detected
if slow_pointer is fast_pointer:
length = 1
# Move slow-pointer by one step to start counting
slow_pointer = slow_pointer.next

# Continue moving the slow pointer until it meets the fast pointer again
while slow_pointer != fast_pointer:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
length += 1
slow_pointer = slow_pointer.next

return length
return 0


def detect_node_with_cycle(head: Optional[Node]) -> Optional[Node]:
"""
Detects a node with a cycle in a Linked List and returns it. The node with a cycle is the entry point of the loop
Args:
head(Node): head node of a linked list
Returns:
Node: node with a cycle if the linked list has a cycle
"""
slow_pointer = fast_pointer = head

while fast_pointer and slow_pointer and fast_pointer.next:
fast_pointer = fast_pointer.next.next
slow_pointer = slow_pointer.next

if slow_pointer == fast_pointer:
break
else:
return None

current = head
while current != slow_pointer:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
slow_pointer = slow_pointer.next
current = current.next
return current


def remove_cycle(head: Optional[Node]) -> Optional[Node]:
"""
Removes cycle from a linked list given the head node
Args:
head(Node): head node of a linked list
Returns:
Node: head node without the cycle
"""
# This is the entry point of the cycle
node_with_cycle = detect_node_with_cycle(head)

# If there is no node with a cycle, return the head node as is
if not node_with_cycle:
return head

# Now, with the node with the cycle, we set a current pointer that will move a node at a time, until its next pointer
# points back to the fixed pointer
current = node_with_cycle
fixed_pointer = node_with_cycle

# Move the pointer on this current until the next pointer reaches the fixed pointer
while current.next != fixed_pointer:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
current = current.next

# Remove the cycle by setting the next to None
current.next = None

return head
Loading