Skip to content

Commit 9f513c9

Browse files
committed
solve Insert into a Sorted Circular Linked List
1 parent 0fce668 commit 9f513c9

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Definition for a Node.
2+
class Node:
3+
def __init__(self, val=None, next=None):
4+
self.val = val
5+
self.next = next
6+
7+
8+
class Solution:
9+
# These are the cases:
10+
# 1. No nodes. (insert head)
11+
# 2. At least two different nodes.
12+
# - curr.val <= insertVal <= curr.next.val (insert to curr.next)
13+
# - curr.val > curr.next.val (insert to curr.next)
14+
# 3. All nodes same. (insert to head.next)
15+
#
16+
# Time Complexity: O(n), Two Pass.
17+
# Space Complexity: O(1).
18+
def insert(self, head: "Node", insertVal: int) -> "Node":
19+
if head is None:
20+
head = Node(val=insertVal)
21+
head.next = head
22+
return head
23+
24+
curr = head
25+
while True:
26+
if curr.val <= insertVal <= curr.next.val:
27+
curr.next = Node(val=insertVal, next=curr.next)
28+
return head
29+
curr = curr.next
30+
if curr == head:
31+
break
32+
33+
curr = head
34+
while True:
35+
if curr.val > curr.next.val:
36+
curr.next = Node(val=insertVal, next=curr.next)
37+
return head
38+
curr = curr.next
39+
if curr == head:
40+
break
41+
42+
head.next = Node(val=insertVal, next=head.next)
43+
return head
44+
45+
46+
class OfficialSolution:
47+
def insert(self, head: Node, insertVal: int) -> Node:
48+
"""
49+
== Approach 1: Two-Pointers Iteration ==
50+
51+
== Intuition ==
52+
As simple as the problem might seem to be, it is actually not trivial to write
53+
a solution that covers all cases.
54+
Often the case for the problems with linked list, one could apply the approach
55+
of Two-Pointers Iteration, where one uses two pointers as surrogate to traverse
56+
the linked list.
57+
One of the reasons of having two pointers rather than one is that in singly
58+
linked list one does not have a reference to the precedent node, therefore we
59+
keep an additional pointer which points to the precedent node.
60+
For this problem, we iterate through the cyclic list using two pointers, namely
61+
prev and curr. When we find a suitable place to insert the new value, we insert
62+
it between the prev and curr nodes.
63+
64+
== Algorithm ==
65+
First of all, let us define the skeleton of two pointers iteration algorithm as
66+
follows:
67+
- As we mentioned in the intuition, we loop over the linked list with two
68+
pointers (i.e. prev and curr) step by step. The termination condition of
69+
the loop is that we get back to the starting point of the two pointers.
70+
(i.e. prev == head).
71+
- During the loop, at each step, we check if the current place bounded by
72+
the two pointers is the right place to insert the new value.
73+
- If not, we move both pointers one step forwards.
74+
75+
Now the tricky part of this problem is to sort out different cases that our
76+
algorithm should deal with within the loop, and then design a concise logic to
77+
handle them sound and properly. Here we break it down into three general cases.
78+
79+
Case 1. The value of new node sits between the minimal and maximal values of
80+
the current list. As a result, it should be inserted within the list.
81+
The condition is to find the place that meets the constraint of:
82+
prev.val <= insertVal <= curr.val.
83+
84+
Case 2. The value of new node goes beyond the minimal and maximal values of the
85+
current list, either less than the minimal value or greater than the maximal
86+
value. In either case, the new node should be added right after the tail node (
87+
i.e. the node with the maximal value of the list).
88+
Firstly, we should locate the position of the tail node, by finding a descending
89+
order between the adjacent, i.e. the condition of prev.val > curr.val, since the
90+
nodes are sorted in ascending order, the tail node would have the greatest value
91+
of all nodes.
92+
93+
Case 3. Finally, there is one case that does not fall into any of the above two
94+
cases. This is the case where the list contains uniform values.
95+
Though not explicitly stated in the problem description, our sorted list can
96+
contain some duplicate values. And in the extreme case, the entire list has only
97+
one single unique value.
98+
99+
The above three cases cover the scenarios within and after our iteration loop.
100+
There is however one minor corner case we still need to deal with, where we have
101+
an empty list. This, we could easily handle before the loop.
102+
103+
== Complexity Analysis ==
104+
- Time Complexity: O(n), where N is the size of the list. In the worst case, we
105+
would iterate through the entire list.
106+
- Space Complexity: O(1). It is a constant space solution.
107+
108+
== Own Comments ==
109+
It is exactly similar to my solution, with
110+
insertVal >= prev.val or insertVal <= curr.val
111+
making it a one pass solution.
112+
"""
113+
if head is None:
114+
newNode = Node(val=insertVal, next=None)
115+
newNode.next = newNode
116+
return newNode
117+
118+
prev, curr = head, head.next
119+
toInsert = False
120+
121+
while True:
122+
if prev.val <= insertVal <= curr.val:
123+
# Case #1
124+
toInsert = True
125+
elif prev.val > curr.val:
126+
# Case #2. Where we locate the tail element
127+
# 'prev' points to the tail, i.e. the largest element
128+
if insertVal >= prev.val or insertVal <= curr.val:
129+
toInsert = True
130+
131+
if toInsert:
132+
prev.next = Node(val=insertVal, next=curr)
133+
return head
134+
135+
prev, curr = curr, curr.next
136+
# loop condition
137+
if prev == head:
138+
break
139+
140+
# Case #3.
141+
# did not insert the node in the loop
142+
prev.next = Node(val=insertVal, next=curr)
143+
return head
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import unittest
2+
3+
from insert_into_a_sorted_circular_linked_list import Solution, Node
4+
5+
6+
class TestInsertIntoASortedCircularLinkedList(unittest.TestCase):
7+
def test_example_1(self):
8+
head = Node(val=1)
9+
head.next = Node(val=3)
10+
head.next.next = Node(val=4)
11+
head.next.next.next = head
12+
node = Solution().insert(head=head, insertVal=2)
13+
assert node.val == 1
14+
assert node.next.val == 2
15+
assert node.next.next.val == 3
16+
assert node.next.next.next.val == 4
17+
assert node.next.next.next.next == node
18+
19+
def test_example_2(self):
20+
node = Solution().insert(head=None, insertVal=2)
21+
assert node.val == 2
22+
assert node.next == node
23+
24+
def test_example_3(self):
25+
head = Node(val=1)
26+
head.next = head
27+
node = Solution().insert(head=head, insertVal=2)
28+
assert node.val == 1
29+
assert node.next.val == 2
30+
assert node.next.next == node

0 commit comments

Comments
 (0)