Skip to content

Commit ed7f4af

Browse files
committed
refactor(datastructures, linkedlist): add utility for splitting circular linked list
1 parent 69df8e4 commit ed7f4af

13 files changed

Lines changed: 160 additions & 1 deletion

datastructures/graphs/undirected/clone_graph/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44

55
def clone(root: Optional[Node]) -> Optional[Node]:
6-
def clone_helper(n: Optional[Node], nodes_cloned: Dict[Node, Node]) -> Optional[Node]:
6+
def clone_helper(
7+
n: Optional[Node], nodes_cloned: Dict[Node, Node]
8+
) -> Optional[Node]:
79
# If the node is None, return None
810
if n is None:
911
return None
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Circular Linked List
2+
3+
A circular linked list is a linked list with a cycle such that the tail node points back to the head node forming a complete
4+
cycle.
5+
6+
---
7+
8+
## Split a Circular Linked List
9+
10+
Given a circular linked list, list, of positive integers, split it into two circular linked lists. The first circular
11+
linked list should contain the first half of the nodes (exactly ⌈list.length / 2⌉ nodes) in the same order they appeared
12+
in the original list, while the second circular linked list should include the remaining nodes in the same order.
13+
14+
Return an array, answer, of length 2, where:
15+
16+
- answer[0] is the head of the circular linked list representing the first half.
17+
- answer[1] is the head of the circular linked list representing the second half.
18+
19+
> Note: A circular linked list is a standard linked list where the last node points back to the first node.
20+
21+
### Constraints
22+
23+
Constraints:
24+
25+
Let n be the number of nodes in a linked list.
26+
27+
- 2 ≤ `n` ≤ 10^3
28+
- 0 ≤ `Node.value` ≤ 10^5
29+
- `LastNode.next = FirstNode` where `LastNode` is the last node of the list and `FirstNode` is the first one
30+
31+
### Solution
32+
33+
The core idea is to use the slow and fast pointer approach, a well-known technique for finding the middle of a linked
34+
list in an optimized manner. The slow pointer moves one step at a time, while the fast pointer moves two steps,
35+
ensuring that when the fast pointer completes its traversal, the slow pointer will be at the midpoint. Once the middle
36+
is found, we break the circular connection at this midpoint, forming two separate lists. The first half starts from the
37+
head and extends up to the slow pointer, with its last node pointing back to the head to maintain the circular structure.
38+
The second half begins from the next node after the slow pointer and extends to the original last node, which is then
39+
linked back to this second half’s new head, ensuring both resulting lists remain circular. Finally, the two newly formed
40+
circular linked lists are returned as separate head pointers representing the start of each half.
41+
42+
Now, let’s walk through the steps of the solution:
43+
44+
1. We initialize the slow and fast pointers to the head of the list. The slow pointer moves one node at a time while the
45+
fast pointer moves two nodes at a time.
46+
2. We iterate through the list using the fast and slow pointers until the fast pointer has reached back to the head,
47+
ensured by the conditions fast.next != head and fast.next.next != head.
48+
3. After iterating, the slow pointer will be at the middle point of the list, while the fast pointer will be pointing
49+
back to the head. This middle point node serves as the point where we will split the list into two halves.
50+
4. The first circular linked list will start from the original head (head1 = head). Before modifying slow.next, we store
51+
slow.next in head2 to retain the starting node of the second half. Then, we update slow.next to point back to head1,
52+
effectively closing the first circular half.
53+
5. The second half of the list begins from the node immediately following the middle point, which we stored in head2 in
54+
the previous step. This prevents losing the reference to the second half’s start after updating slow.next for the
55+
first half.
56+
6. Next, we need to ensure that the second half is also circular. To do this, we traverse the second half starting from
57+
head2 using the fast pointer. The fast moves throughout the list until it points back to the head.
58+
7. Once the fast pointer reaches the head, we update fast.next=head2, closing the second circular list.
59+
8. Finally, we return the heads of two split circular linked lists as an array: [head1, head2].
60+
61+
![Solution 1](./images/solutions/split_circular_linked_list_solution_1.png)
62+
![Solution 2](./images/solutions/split_circular_linked_list_solution_2.png)
63+
![Solution 3](./images/solutions/split_circular_linked_list_solution_3.png)
64+
![Solution 4](./images/solutions/split_circular_linked_list_solution_4.png)
65+
![Solution 5](./images/solutions/split_circular_linked_list_solution_5.png)
66+
![Solution 6](./images/solutions/split_circular_linked_list_solution_6.png)
67+
![Solution 7](./images/solutions/split_circular_linked_list_solution_7.png)
68+
![Solution 8](./images/solutions/split_circular_linked_list_solution_8.png)
69+
70+
#### Time Complexity
71+
72+
The time complexity of the solution is `O(n)`, where `n` is the total number of nodes in the circular linked list. This
73+
is because we make two passes over the list—one to count the nodes and another to split the list.
74+
75+
#### Space Complexity
76+
77+
The space complexity is `O(1)` because the solution uses constant space regardless of the input size.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import List
2+
from datastructures.linked_lists.circular.node import CircularNode
3+
4+
5+
def split_circular_linked_list(head: CircularNode) -> List[CircularNode]:
6+
# Initialize two pointers for the traversal: slow and fast
7+
slow = fast = head
8+
9+
# Traverse the circular linked list to find the middle point
10+
while fast.next != head and fast.next.next != head:
11+
slow = slow.next
12+
fast = fast.next.next
13+
14+
# At this point, slow is at the middle of the list
15+
head1 = head
16+
head2 = slow.next
17+
18+
# Split the circular linked list into two halves
19+
slow.next = head1
20+
# To complete the second half, traverse to its end
21+
fast = head2
22+
while fast.next != head:
23+
fast = fast.next
24+
fast.next = head2
25+
# Return the heads of the two split circular linked lists
26+
return [head1, head2]
48.3 KB
Loading
60.2 KB
Loading
67.4 KB
Loading
67.9 KB
Loading
98.5 KB
Loading
72.4 KB
Loading
70.3 KB
Loading

0 commit comments

Comments
 (0)