Skip to content

Commit 4fc7f7f

Browse files
committed
miguel soln 2.7 intersection
1 parent 8ad3fff commit 4fc7f7f

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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
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+
32+
class LinkedList:
33+
def __init__(self, *numbers: int):
34+
self.head = None
35+
self.tail = None
36+
self.size = 0
37+
for num in numbers:
38+
self.append_to_tail(num)
39+
40+
def append_to_tail(self, e) -> None:
41+
if isinstance(e, int):
42+
self._append_num(e)
43+
elif isinstance(e, Node):
44+
self._append_node(e)
45+
46+
def _append_num(self, d: int) -> None:
47+
if self.head is None:
48+
self.head = Node(d)
49+
self.tail = self.head
50+
else:
51+
end = Node(d)
52+
self.tail.next = end
53+
self.tail = end
54+
self.size += 1
55+
56+
def _append_node(self, n: Node) -> None:
57+
if self.head is None:
58+
self.head = n
59+
self.tail = self.head
60+
else:
61+
end = n
62+
self.tail.next = end
63+
self.tail = end
64+
self.size += 1
65+
66+
def append_to_head(self, d: int) -> None:
67+
new_head = Node(d)
68+
new_head.next = self.head
69+
if self.head is None:
70+
# if list is empty and we add
71+
# out first element, head AND tail
72+
# must point to same node
73+
self.tail = new_head
74+
self.head = new_head
75+
self.size += 1
76+
77+
def get_node_at(self, index: int) -> Node:
78+
if index < 0 or index >= self.size:
79+
raise IndexError('list index out of range')
80+
n = self.head
81+
for i in range(self.size):
82+
if i == index:
83+
return n
84+
n = n.next
85+
86+
def get_value_at(self, index: int) -> int:
87+
if index < 0 or index >= self.size:
88+
raise IndexError('list index out of range')
89+
n = self.head
90+
for i in range(self.size):
91+
if i == index:
92+
return n.data
93+
n = n.next
94+
95+
def pop_head(self) -> Node:
96+
if self.head is None:
97+
raise IndexError('no head to pop')
98+
h = self.head
99+
h.next = None
100+
self.head = self.head.next
101+
self.size -= 1
102+
return h
103+
104+
def append(self, ll: 'LinkedList') -> None:
105+
self.tail.next = ll.head
106+
self.size += ll.size
107+
ll.head = None
108+
ll.size = 0
109+
110+
def reverse(self) -> None:
111+
"""
112+
Reverses this linked list in place
113+
:return:
114+
"""
115+
if self.head is None:
116+
return
117+
prev = self.head
118+
self.tail = prev
119+
curr = prev.next
120+
self.tail.next = None
121+
while curr is not None:
122+
old_next = curr.next
123+
curr.next = prev
124+
prev = curr
125+
curr = old_next
126+
self.head = prev
127+
128+
def __repr__(self):
129+
return self.__str__()
130+
131+
def __str__(self):
132+
if self.head is None:
133+
return '<empty>'
134+
ll = []
135+
n = self.head
136+
while n.next is not None:
137+
ll.append('{} -> '.format(n.data))
138+
n = n.next
139+
ll.append(str(n.data))
140+
return ''.join(ll)
141+
142+
def __eq__(self, other: object):
143+
if not isinstance(other, LinkedList):
144+
return NotImplemented
145+
a = self.head
146+
b = other.head
147+
while a is not None and b is not None:
148+
if a.data != b.data:
149+
return False
150+
# otherwise, advance both pointers
151+
a = a.next
152+
b = b.next
153+
return a is None and b is None
154+
155+
156+
def intersection(ll1: LinkedList, ll2: LinkedList) -> Optional[Node]:
157+
"""
158+
This function will determine if ll1 and ll2 intersect.
159+
The intersection is defined based on reference, not value.
160+
Runtime: O(n^2)
161+
Space Complexity: O(1)
162+
:param ll1: first input linked list
163+
:param ll2: second input linked list
164+
:return: The intersecting node or None
165+
"""
166+
n1 = ll1.head
167+
while n1 is not None:
168+
n2 = ll2.head
169+
while n2 is not None:
170+
if n1 is n2:
171+
return n1
172+
n2 = n2.next
173+
n1 = n1.next
174+
return None
175+
176+
177+
class TestIntersection(unittest.TestCase):
178+
179+
def setUp(self):
180+
shared_structures = [
181+
(
182+
LinkedList(1, 2, 3),
183+
LinkedList(5, 10, 11),
184+
Node(4),
185+
LinkedList(6, 7, 8, 9),
186+
),
187+
(
188+
LinkedList(1, 2, 3, 4, 5),
189+
LinkedList(7),
190+
Node(6),
191+
LinkedList(9, 8, 7),
192+
),
193+
(
194+
LinkedList(1, 2),
195+
LinkedList(4, 5),
196+
Node(3),
197+
LinkedList(9, 8, 7, 6, 7, 6, 7, 9),
198+
)
199+
]
200+
201+
self.intersection_test_cases = []
202+
203+
for ll1_seg1, ll1_seg2, shared_node, ll2 in shared_structures:
204+
ll1_seg1.append_to_tail(shared_node)
205+
ll1_seg1.append(ll1_seg2)
206+
ll2.append_to_tail(shared_node)
207+
self.intersection_test_cases.append((ll1_seg1, ll2, shared_node))
208+
209+
self.no_intersection_test_cases = [
210+
(
211+
LinkedList(1, 2, 3, 4, 5),
212+
LinkedList(1, 2, 3)
213+
),
214+
(
215+
LinkedList(66, 2, 12, 35),
216+
LinkedList(79, 19, 23, 24)
217+
),
218+
(
219+
LinkedList(),
220+
LinkedList(1, 2, 3, 4)
221+
)
222+
]
223+
224+
def test_intersections(self):
225+
for ll1, ll2, shared_node in self.intersection_test_cases:
226+
result_node = intersection(ll1, ll2)
227+
self.assertTrue(result_node is shared_node, msg=(ll1, ll2, shared_node))
228+
229+
for ll1, ll2 in self.no_intersection_test_cases:
230+
self.assertIsNone(intersection(ll1, ll2), msg=(ll1, ll2))
231+
232+
233+
if __name__ == '__main__':
234+
unittest.main()

0 commit comments

Comments
 (0)