Skip to content

Commit fc50dd4

Browse files
Add 'Merge k Sorted Lists'
1 parent d747a68 commit fc50dd4

File tree

4 files changed

+154
-4
lines changed

4 files changed

+154
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ A collection of LeetCode solutions
3030

3131
[Maximum Subarray](./src/maximum_subarray.py)
3232

33+
[Merge k Sorted Lists](./src/merge_k_sorted_lists.py)
34+
3335
[Merge Two Sorted Lists](./src/merge_two_sorted_lists.py)
3436

3537
[Middle of the Linked List](./src/middle_of_the_linked_list.py)

TODO.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
- [x] Add unit tests
66
- [ ] Solve 'Same Tree' using recursion (DFS)
77
- [ ] Review other approaches to solving 'Subtree of Another Tree'
8-
- [ ] Add alternative solution for 'Longest Increasing Subsequence'
9-
- [ ] Add binary search solution for 'Longest Increasing Subsequence'
8+
- [ ] Add alternative approach to 'Longest Increasing Subsequence'
9+
- [ ] Add binary search approach to 'Longest Increasing Subsequence'
1010
- [ ] Ensure all lines are < 80 characters.
11-
- [ ] Add recursive solution to 'Reverse Linked List'
12-
- [ ] Add recursive solution to 'Merge Two Sorted Lists'
11+
- [ ] Add recursive approach to 'Reverse Linked List'
12+
- [ ] Add recursive approach to 'Merge Two Sorted Lists'
13+
- [ ] Add a *divide and conquer* approach to 'Merge k Sorted Lists'. This approach has O(nlog n) time complexity, but O(1) space complexity.

src/merge_k_sorted_lists.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
23. Merge k Sorted Lists
3+
4+
https://leetcode.com/problems/merge-k-sorted-lists
5+
6+
NOTES
7+
* The naive solution to merge k sorted lists is an extension of the solution
8+
for merging two sorted lists. However, for k lists the time complexity
9+
becomes non-linear (O(nk)), since every selection costs O(k). To optimize
10+
the selection process we can use a priority queue, which is implemented as
11+
a min-heap. A heap offers O(log n) performance for inserts and removals,
12+
and O(n) to build the heap initially from a set of n elements.
13+
14+
To start, we build a heap using the head of each list. Next, for each
15+
selection, we use the find-min operation (O(1)) and delete-min operation
16+
(O(log k)), removing the minimum value from the heap. Finally, the head of
17+
the list from which the node was taken is inserted into the heap. The
18+
selection now has the following time complexity:
19+
20+
O(k) + O(1) + O(log k) + O(log k) = O(log k)
21+
22+
This gives an overall time complexity of O(nlog k) and O(k) space
23+
complexity for the heap.
24+
25+
Example (where ○ represents null):
26+
27+
1 4 5
28+
● → ● → ● → ○
29+
30+
1 3 4
31+
● → ● → ● → ○
32+
33+
2 6
34+
● → ● → ○
35+
36+
Build a heap using the head of each list:
37+
38+
1
39+
40+
/ \
41+
1 2
42+
● ●
43+
44+
For each selection, remove the minimum value from the heap:
45+
46+
4 5
47+
● → ● → ○
48+
49+
50+
1 1 3 4
51+
○ → ● ● → ● → ● → ○
52+
↑ ↑
53+
54+
2 6
55+
● → ● → ○
56+
57+
58+
Insert the head of the list from which the node was taken into the heap:
59+
60+
1
61+
62+
/ \
63+
2 4
64+
● ●
65+
"""
66+
67+
import heapq
68+
69+
from src.classes import ListNode
70+
71+
72+
class HeapNode:
73+
"""
74+
Wrapper for a ListNode.
75+
76+
Enables heap operations by implementing the '<' operation.
77+
"""
78+
79+
def __init__(self, node: ListNode):
80+
self.node: ListNode = node
81+
82+
def __lt__(self, other: "HeapNode") -> bool:
83+
return self.node.val < other.node.val
84+
85+
86+
class Solution:
87+
def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None:
88+
if not lists:
89+
return None
90+
91+
# Build a heap using the head of each list.
92+
heap: list[HeapNode] = []
93+
for head in lists:
94+
if head:
95+
heapq.heappush(heap, HeapNode(node=head))
96+
97+
# Instantiate a head sentinel node.
98+
prehead = ListNode(-1)
99+
curr = prehead
100+
101+
# For each selection, use `heappop`, which removes the smallest element
102+
# from the heap, maintaining the heap invariant. The head of the list
103+
# from which the node was taken is inserted into the heap.
104+
while heap:
105+
_next = heapq.heappop(heap).node
106+
head = _next.next
107+
curr.next = _next
108+
curr = curr.next
109+
if head:
110+
heapq.heappush(heap, HeapNode(node=head))
111+
112+
return prehead.next

tests/test_merge_k_sorted_lists.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
23. Merge k Sorted Lists
3+
4+
https://leetcode.com/problems/merge-k-sorted-lists
5+
"""
6+
7+
from unittest import TestCase
8+
9+
from src.merge_k_sorted_lists import Solution
10+
11+
from .utils import create_linked_list_from_list, create_list_from_linked_list
12+
13+
14+
class TestSolution(TestCase):
15+
def test_1(self):
16+
exp = [1, 1, 2, 3, 4, 4, 5, 6]
17+
ls = [[1, 4, 5], [1, 3, 4], [2, 6]]
18+
lls = [create_linked_list_from_list(l) for l in ls]
19+
ll = Solution().mergeKLists(lls)
20+
assert create_list_from_linked_list(ll) == exp
21+
22+
def test_2(self):
23+
exp = []
24+
ls = []
25+
lls = []
26+
lls = [create_linked_list_from_list(l) for l in ls]
27+
ll = Solution().mergeKLists(lls)
28+
assert create_list_from_linked_list(ll) == exp
29+
30+
def test_3(self):
31+
exp = []
32+
ls = [[]]
33+
lls = [create_linked_list_from_list(l) for l in ls]
34+
ll = Solution().mergeKLists(lls)
35+
assert create_list_from_linked_list(ll) == exp

0 commit comments

Comments
 (0)