Skip to content

Commit 1803cea

Browse files
authored
Merge pull request #43 from miguelHx/miguel_2.7_intersection
Miguel 2.7 - Intersection [Python]
2 parents 8ad3fff + f789eb6 commit 1803cea

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
"""
2+
Python version 3.7.0
3+
2.7 - Intersection
4+
Given two (singly) linked lists, determine if the two
5+
lists intersect. Return the intersecting node.
6+
Note that the intersection is defined based on reference,
7+
not value. That is, if the kth node of the first linked
8+
list is the exact same node (by reference) as the jth
9+
node of the second linked list, then they are intersecting.
10+
"""
11+
import unittest
12+
from typing import Optional, NamedTuple
13+
14+
15+
class Node:
16+
def __init__(self, d: int):
17+
self.data = d
18+
self.next = None
19+
20+
def __repr__(self):
21+
return self.__str__()
22+
23+
def __str__(self):
24+
return '<Node Value: {}>'.format(self.data)
25+
26+
def __eq__(self, other: object):
27+
if not isinstance(other, Node):
28+
return NotImplemented
29+
return self.data == other.data
30+
31+
def __hash__(self):
32+
"""
33+
Hash based on node's memory address.
34+
:return:
35+
"""
36+
return id(self)
37+
38+
39+
class LinkedList:
40+
def __init__(self, *numbers: int):
41+
self.head = None
42+
self.tail = None
43+
self.size = 0
44+
for num in numbers:
45+
self.append_to_tail(num)
46+
47+
def append_to_tail(self, e) -> None:
48+
if isinstance(e, int):
49+
self._append_num(e)
50+
elif isinstance(e, Node):
51+
self._append_node(e)
52+
53+
def _append_num(self, d: int) -> None:
54+
if self.head is None:
55+
self.head = Node(d)
56+
self.tail = self.head
57+
else:
58+
end = Node(d)
59+
self.tail.next = end
60+
self.tail = end
61+
self.size += 1
62+
63+
def _append_node(self, n: Node) -> None:
64+
if self.head is None:
65+
self.head = n
66+
self.tail = self.head
67+
else:
68+
end = n
69+
self.tail.next = end
70+
self.tail = end
71+
self.size += 1
72+
73+
def append_to_head(self, d: int) -> None:
74+
new_head = Node(d)
75+
new_head.next = self.head
76+
if self.head is None:
77+
# if list is empty and we add
78+
# out first element, head AND tail
79+
# must point to same node
80+
self.tail = new_head
81+
self.head = new_head
82+
self.size += 1
83+
84+
def get_node_at(self, index: int) -> Node:
85+
if index < 0 or index >= self.size:
86+
raise IndexError('list index out of range')
87+
n = self.head
88+
for i in range(self.size):
89+
if i == index:
90+
return n
91+
n = n.next
92+
93+
def get_value_at(self, index: int) -> int:
94+
if index < 0 or index >= self.size:
95+
raise IndexError('list index out of range')
96+
n = self.head
97+
for i in range(self.size):
98+
if i == index:
99+
return n.data
100+
n = n.next
101+
102+
def pop_head(self) -> Node:
103+
if self.head is None:
104+
raise IndexError('no head to pop')
105+
h = self.head
106+
h.next = None
107+
self.head = self.head.next
108+
self.size -= 1
109+
return h
110+
111+
def append(self, ll: 'LinkedList') -> None:
112+
self.tail.next = ll.head
113+
self.size += ll.size
114+
ll.head = None
115+
ll.size = 0
116+
117+
def reverse(self) -> None:
118+
"""
119+
Reverses this linked list in place
120+
:return:
121+
"""
122+
if self.head is None:
123+
return
124+
prev = self.head
125+
self.tail = prev
126+
curr = prev.next
127+
self.tail.next = None
128+
while curr is not None:
129+
old_next = curr.next
130+
curr.next = prev
131+
prev = curr
132+
curr = old_next
133+
self.head = prev
134+
135+
def __repr__(self):
136+
return self.__str__()
137+
138+
def __str__(self):
139+
if self.head is None:
140+
return '<empty>'
141+
ll = []
142+
n = self.head
143+
while n.next is not None:
144+
ll.append('{} -> '.format(n.data))
145+
n = n.next
146+
ll.append(str(n.data))
147+
return ''.join(ll)
148+
149+
def __eq__(self, other: object):
150+
if not isinstance(other, LinkedList):
151+
return NotImplemented
152+
a = self.head
153+
b = other.head
154+
while a is not None and b is not None:
155+
if a.data != b.data:
156+
return False
157+
# otherwise, advance both pointers
158+
a = a.next
159+
b = b.next
160+
return a is None and b is None
161+
162+
163+
def intersection_linear_runtime(ll1: LinkedList, ll2: LinkedList) -> Optional[Node]:
164+
"""
165+
This function will determine if ll1 and ll2 intersect.
166+
The intersection is defined based on reference, not value.
167+
Runtime: O(n)
168+
Space Complexity: O(n)
169+
:param ll1: first input linked list
170+
:param ll2: second input linked list
171+
:return: The intersecting node or None
172+
"""
173+
n1 = ll1.head
174+
refs = set()
175+
while n1 is not None:
176+
refs.add(n1)
177+
n1 = n1.next
178+
n2 = ll2.head
179+
while n2 is not None:
180+
if n2 in refs:
181+
return n2
182+
n2 = n2.next
183+
return None
184+
185+
186+
def intersection(ll1: LinkedList, ll2: LinkedList) -> Optional[Node]:
187+
"""
188+
This function will determine if ll1 and ll2 intersect.
189+
The intersection is defined based on reference, not value.
190+
Runtime: O(n^2)
191+
Space Complexity: O(1)
192+
:param ll1: first input linked list
193+
:param ll2: second input linked list
194+
:return: The intersecting node or None
195+
"""
196+
n1 = ll1.head
197+
while n1 is not None:
198+
n2 = ll2.head
199+
while n2 is not None:
200+
if n1 is n2:
201+
return n1
202+
n2 = n2.next
203+
n1 = n1.next
204+
return None
205+
206+
207+
class SharedLLStructure(NamedTuple):
208+
first_segment: LinkedList
209+
second_segment: LinkedList
210+
shared_node: Node
211+
other_list: LinkedList
212+
213+
214+
class TestIntersection(unittest.TestCase):
215+
216+
def setUp(self):
217+
shared_structures = [
218+
SharedLLStructure(
219+
LinkedList(1, 2, 3),
220+
LinkedList(5, 10, 11),
221+
Node(4),
222+
LinkedList(6, 7, 8, 9)
223+
),
224+
SharedLLStructure(
225+
LinkedList(1, 2, 3, 4, 5),
226+
LinkedList(7),
227+
Node(6),
228+
LinkedList(9, 8, 7),
229+
),
230+
SharedLLStructure(
231+
LinkedList(1, 2),
232+
LinkedList(4, 5),
233+
Node(3),
234+
LinkedList(9, 8, 7, 6, 7, 6, 7, 9),
235+
)
236+
]
237+
238+
self.intersection_test_cases = []
239+
240+
for s in shared_structures:
241+
s.first_segment.append_to_tail(s.shared_node)
242+
s.first_segment.append(s.second_segment)
243+
s.other_list.append_to_tail(s.shared_node)
244+
self.intersection_test_cases.append((s.first_segment, s.other_list, s.shared_node))
245+
246+
self.no_intersection_test_cases = [
247+
(
248+
LinkedList(1, 2, 3, 4, 5),
249+
LinkedList(1, 2, 3)
250+
),
251+
(
252+
LinkedList(66, 2, 12, 35),
253+
LinkedList(79, 19, 23, 24)
254+
),
255+
(
256+
LinkedList(),
257+
LinkedList(1, 2, 3, 4)
258+
),
259+
(
260+
LinkedList(1, 2, 3, 4),
261+
LinkedList(1, 2, 3, 4)
262+
)
263+
]
264+
265+
def test_intersections(self):
266+
for ll1, ll2, shared_node in self.intersection_test_cases:
267+
result_node = intersection(ll1, ll2)
268+
self.assertTrue(result_node is shared_node, msg=(ll1, ll2, shared_node))
269+
270+
for ll1, ll2 in self.no_intersection_test_cases:
271+
self.assertIsNone(intersection(ll1, ll2), msg=(ll1, ll2))
272+
273+
def test_intersection_linear_runtime(self):
274+
for ll1, ll2, shared_node in self.intersection_test_cases:
275+
result_node = intersection_linear_runtime(ll1, ll2)
276+
self.assertTrue(result_node is shared_node, msg=(ll1, ll2, shared_node))
277+
278+
for ll1, ll2 in self.no_intersection_test_cases:
279+
self.assertIsNone(intersection_linear_runtime(ll1, ll2), msg=(ll1, ll2))
280+
281+
282+
if __name__ == '__main__':
283+
unittest.main()

0 commit comments

Comments
 (0)