1- from typing import Optional
1+ from typing import Optional , Callable , Any
22from datastructures .linked_lists import Node
33
44
@@ -19,3 +19,157 @@ def find_middle_node(head: Optional[Node]) -> Optional[Node]:
1919 fast_pointer = fast_pointer .next .next
2020
2121 return slow_pointer
22+
23+
24+ def has_cycle (
25+ head : Optional [Node ], func : Optional [Callable [[Node , Node ], Any ]] = None
26+ ) -> bool :
27+ f"""
28+ 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
29+ reached again after traversing the entire linked list
30+ Args:
31+ head(Node): head node of a linked list
32+ func(Callable): An optional callable function that if passed in will be executed
33+ Returns:
34+ bool: True if there is a cycle, False otherwise
35+ """
36+ if not head or not head .next :
37+ return False
38+
39+ fast_pointer , slow_pointer = head , head
40+ while fast_pointer and fast_pointer .next :
41+ slow_pointer = slow_pointer .next
42+ fast_pointer = fast_pointer .next .next
43+
44+ if slow_pointer is fast_pointer :
45+ # do something after detecting cycle passing in the two nodes
46+ if func :
47+ func (slow_pointer , fast_pointer )
48+ return True
49+ return False
50+
51+
52+ def cycle_length (head : Optional [Node ]) -> int :
53+ """
54+ Determines the length of the cycle in a linked list if it has one. The length of the cycle is the number
55+ of nodes that are 'caught' in the cycle
56+ Args:
57+ head(Node): head node of linked list
58+ Returns:
59+ int: length of the cycle or number of nodes in the cycle
60+ """
61+ if not head :
62+ return 0
63+ slow_pointer = fast_pointer = head
64+
65+ while fast_pointer and fast_pointer .next :
66+ slow_pointer = slow_pointer .next
67+ fast_pointer = fast_pointer .next .next
68+
69+ # Cycle detected
70+ if slow_pointer is fast_pointer :
71+ length = 1
72+ # Move slow-pointer by one step to start counting
73+ slow_pointer = slow_pointer .next
74+
75+ # Continue moving the slow pointer until it meets the fast pointer again
76+ while slow_pointer is not fast_pointer :
77+ length += 1
78+ slow_pointer = slow_pointer .next
79+
80+ return length
81+ return 0
82+
83+
84+ def detect_node_with_cycle (head : Optional [Node ]) -> Optional [Node ]:
85+ """
86+ 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
87+ Args:
88+ head(Node): head node of a linked list
89+ Returns:
90+ Node: node with a cycle if the linked list has a cycle
91+ """
92+ slow_pointer = fast_pointer = head
93+
94+ while fast_pointer and slow_pointer and fast_pointer .next :
95+ fast_pointer = fast_pointer .next .next
96+ slow_pointer = slow_pointer .next
97+
98+ if slow_pointer == fast_pointer :
99+ break
100+ else :
101+ return None
102+
103+ current = head
104+ while current is not slow_pointer :
105+ slow_pointer = slow_pointer .next
106+ current = current .next
107+ return current
108+
109+
110+ def remove_cycle (head : Optional [Node ]) -> Optional [Node ]:
111+ """
112+ Removes cycle from a linked list given the head node
113+ Args:
114+ head(Node): head node of a linked list
115+ Returns:
116+ Node: head node without the cycle
117+ """
118+ # This is the entry point of the cycle
119+ node_with_cycle = detect_node_with_cycle (head )
120+
121+ # If there is no node with a cycle, return the head node as is
122+ if not node_with_cycle :
123+ return head
124+
125+ # Now, with the node with the cycle, we set a current pointer that will move a node at a time, until its next pointer
126+ # points back to the fixed pointer
127+ current = node_with_cycle
128+ fixed_pointer = node_with_cycle
129+
130+ # Move the pointer on this current until the next pointer reaches the fixed pointer
131+ while current .next is not fixed_pointer :
132+ current = current .next
133+
134+ # Remove the cycle by setting the next to None
135+ current .next = None
136+
137+ return head
138+
139+ def remove_nth_from_end (head : Optional [Node ], n : int ) -> Optional [Node ]:
140+ """
141+ Removes the nth node from a linked list from the head given the head of the linked list and the position from the
142+ end of the linked list to remove.
143+ Args:
144+ head(Node): head node of linkedlist
145+ n(int): the position of the last node from the tail in the linked list to remove
146+ Returns:
147+ Node: head node of modified linked list
148+ """
149+ if not head :
150+ return head
151+
152+ # Initialize two pointers, both starting at the head node
153+ fast = slow = head
154+
155+ # Move the fast pointer until it reaches position n in the linked list
156+ for _ in range (n ):
157+ fast = fast .next
158+
159+ # If there is no node at this pointers position, then we have reached the end of the linked list and we return the
160+ # next node from the head. This means, we are removing the head node
161+ if not fast :
162+ return head .next
163+
164+ # Move the fast pointer, until it reaches the end of the linked list and until the slow pointer reaches n nodes from
165+ # the end of the linked list
166+ while fast .next :
167+ fast = fast .next
168+ slow = slow .next
169+
170+ # Set the next pointer of the node at the slow pointer's position to the next node's next pointer, removing the node in the middle
171+ slow .next = slow .next .next
172+
173+ # Return the modified head node of the linked list with the node removed
174+ return head
175+
0 commit comments