Skip to content

Commit d89b8ff

Browse files
committed
Linked Lists: intersection
1 parent 78116b1 commit d89b8ff

File tree

3 files changed

+145
-22
lines changed

3 files changed

+145
-22
lines changed

docs/linked-lists.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[Partition]: ../src/algorithmic/llists.py#L177
77
[Sum Lists]: ../src/algorithmic/llists.py#L206
88
[Palindrome]: ../src/algorithmic/llists.py#L275
9+
[Intersection]: ../src/algorithmic/llists.py#L370
910

1011

1112
1. **[Remove duplicates]**: Write code to remove duplicates from an unsorted linked list.
@@ -38,4 +39,9 @@ function that adds the two numbers and returns the sum as a linked list.
3839
- `Input: (7-> 1 -> 6) + (5 -> 9 -> 2). That is, 617 + 295`.
3940
- `Output: 2 -> 1 -> 9. That is, 912`.
4041

41-
6. **[Palindrome]**: Implement a function to check if a linked list is a palindrome.
42+
6. **[Palindrome]**: Implement a function to check if a linked list is a palindrome.
43+
44+
7. **[Intersection]**: Given two (singly) linked lists, determine if the two lists intersect. Return the
45+
intersecting node. Note that the intersection is defined based on reference, not value. That is, if the
46+
kth node of the first linked list is the exact same node (by reference) as the jth node of the second
47+
linked list, then they are intersecting.

src/algorithmic/llists.py

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
from typing import List, Any, Tuple
23

34

@@ -27,7 +28,7 @@ def __init__(self, items: List[Any] = None):
2728
if items is not None:
2829
node = Node(data=items[0])
2930
self.head = node
30-
self.add_last(items[1:])
31+
self.add_items(items[1:])
3132

3233
def __repr__(self):
3334
items = self.tolist()
@@ -43,11 +44,9 @@ def __iter__(self):
4344
node = node.next
4445

4546
def __len__(self):
46-
i = 0
47-
for node in self:
48-
i += 1
47+
size, _ = self.size_and_tail()
4948

50-
return i
49+
return size
5150

5251
def __eq__(self, other):
5352
if not isinstance(other, LinkedList):
@@ -79,7 +78,12 @@ def tolist(self):
7978
def tostring(self):
8079
return ''.join(map(str, self.tolist()))
8180

82-
def add_last(self, items: List[Any]):
81+
def add_node(self, node: Node):
82+
"""Inserts a node at the end of the linked-list."""
83+
last_node = self.get_last()
84+
last_node.next = node
85+
86+
def add_items(self, items: List[Any]):
8387
"""Inserts list of items at the end of the linked list."""
8488
tail = self.head
8589

@@ -100,21 +104,53 @@ def get_node(self, data):
100104

101105
return node
102106

107+
def get_last(self):
108+
"""Returns last node."""
109+
node = None
110+
for n in self:
111+
node = n
112+
113+
return node
114+
115+
def at(self, idx):
116+
"""Returns node at index idx."""
117+
i = 0
118+
for node in self:
119+
if i == idx:
120+
return node
121+
122+
i += 1
123+
124+
raise ValueError("index {} out of bounds for list: {}!".format(idx, self.tolist()))
125+
103126
def reverse(self):
104-
"""Returns the linked listed reversed."""
105-
tail = None
127+
"""Reverses the linked list in-place.
106128
107-
node = self.head
108-
while node is not None:
109-
n = Node(data=node.data)
110-
n.next = tail
111-
tail = n
112-
node = node.next
129+
Complexity:
130+
- Time: O(N).
131+
- Space: O(1)
132+
"""
133+
_prev = None
134+
_current = self.head
113135

114-
llist = LinkedList()
115-
llist.head = tail
136+
while _current is not None:
137+
_next = _current.next
138+
_current.next = _prev
139+
_prev = _current
140+
_current = _next
116141

117-
return llist
142+
self.head = _prev
143+
144+
def size_and_tail(self):
145+
"""Returns the size and tail of linked-list."""
146+
i = 0
147+
node = None
148+
149+
for n in self:
150+
node = n
151+
i += 1
152+
153+
return i, node
118154

119155

120156
def remove_duplicates(llist: LinkedList) -> None:
@@ -272,7 +308,7 @@ def sum(l1: LinkedList, l2: LinkedList) -> LinkedList:
272308
return LinkedList(l3_numbers[::-1])
273309

274310

275-
def is_palindrome(llist: LinkedList, method='reverse') -> bool:
311+
def is_palindrome(llist: LinkedList, method: str = 'reverse') -> bool:
276312
"""Checks if the items in a linked-list form a palindrome.
277313
278314
Complexity:
@@ -285,7 +321,10 @@ def is_palindrome(llist: LinkedList, method='reverse') -> bool:
285321
raise ValueError("Method {} not valid. Choose from {}".format(method, METHODS))
286322

287323
if method == 'reverse':
288-
return llist == llist.reverse()
324+
llist_copy = copy.deepcopy(llist)
325+
llist.reverse()
326+
327+
return llist_copy == llist
289328

290329
elif method == 'iterative':
291330
stack = []
@@ -326,3 +365,36 @@ def recursive(head: Node, length: int):
326365
_, res = recursive(llist.head, len(llist))
327366

328367
return res
368+
369+
370+
def intersection(l1: LinkedList, l2: LinkedList):
371+
"""Finds an intersecting node between l2 and l2 linked-lists."""
372+
373+
l1_len, l1_tail = l1.size_and_tail()
374+
l2_len, l2_tail = l2.size_and_tail()
375+
376+
if l1_len == 0 or l2_len == 0:
377+
return False, None
378+
379+
if l1_tail is not l2_tail:
380+
return False, None
381+
382+
len_diff = abs(l1_len - l2_len)
383+
384+
p1 = l1.head
385+
p2 = l2.head
386+
387+
if l1_len > l2_len:
388+
p1 = l1.at(len_diff)
389+
390+
if l2_len > l1_len:
391+
p2 = l2.at(len_diff)
392+
393+
while p1 is not None:
394+
if p1 is p2:
395+
break
396+
397+
p1 = p1.next
398+
p2 = p2.next
399+
400+
return True, p1

tests/test_llists.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ def test_linked_list():
1515
assert llist.tostring() == "ab"
1616
assert len(llist) == 2
1717

18-
llist.add_last(items2)
18+
with pytest.raises(ValueError):
19+
llist.at(10)
20+
21+
llist.add_items(items2)
1922
assert llist.tolist() == items1 + items2
2023

2124
assert repr(llist.head) == "a"
@@ -24,12 +27,17 @@ def test_linked_list():
2427
two = llists.LinkedList(items2)
2528
assert one == two
2629

27-
two.add_last(items1)
30+
two.add_items(items1)
2831
assert not one == two
2932

3033
with pytest.raises(ValueError):
3134
assert one == "string"
3235

36+
items = ["a", "b"]
37+
llist = llists.LinkedList(items)
38+
llist.reverse()
39+
assert llist.tolist() == items[::-1]
40+
3341

3442
def test_remove_duplicates():
3543
items = ["a", "b", "b", "c", "d", "e", "e"]
@@ -141,3 +149,40 @@ def test_palindrome():
141149
llist = llists.LinkedList(items)
142150
for method in methods:
143151
assert llists.is_palindrome(llist, method=method)
152+
153+
154+
def test_intersection():
155+
items = ["p", "a", "l", "i", "n", "d"]
156+
l1 = llists.LinkedList(items)
157+
node = l1.get_node("i")
158+
159+
# intersection, different length
160+
l2 = llists.LinkedList()
161+
l2.head = node
162+
result, int_node = llists.intersection(l1, l2) # l1 > l2
163+
assert result
164+
assert int_node is node
165+
166+
result, int_node = llists.intersection(l2, l1) # l1 > l2
167+
assert result
168+
assert int_node is node
169+
170+
# no intersection
171+
l2 = llists.LinkedList(["r", "o", "m", "e"])
172+
result, int_node = llists.intersection(l1, l2)
173+
assert not result
174+
assert int_node is None
175+
176+
# intersection, same length
177+
node = l2.get_node("e")
178+
l3 = llists.LinkedList(["z", "w", "x"])
179+
l3.add_node(node)
180+
result, int_node = llists.intersection(l2, l3)
181+
assert result
182+
assert int_node is node
183+
184+
# no intersection, empty list
185+
l4 = llists.LinkedList()
186+
result, int_node = llists.intersection(l3, l4)
187+
assert not result
188+
assert int_node is None

0 commit comments

Comments
 (0)