diff --git a/0023.Merge-k-Sorted-Lists/memo.md b/0023.Merge-k-Sorted-Lists/memo.md new file mode 100644 index 0000000..74f9dc9 --- /dev/null +++ b/0023.Merge-k-Sorted-Lists/memo.md @@ -0,0 +1,108 @@ +# 23. Merge k Sorted Lists + +## step1 + +17mで解けた。紙に書いて考えるうちに最小値を管理する -> heapという発想になった。破壊的な解法になっている。 + +時間計算量: +O(sum(lists[i].length)log k) + +空間計算量: +O(k) + +## step2 + +非破壊的な解法: step2.py + +indexを格納しないと、<が定義されずエラーになる。 + +これを面接のtest環境のないところで気がつくのは、ヒントがないと自分には難しそう。 + +以下の実験でも確認できる。 + +```python +heap = [(0, ListNode()), (0, ListNode())] +heapq.heapify(heap) +# TypeError: '<' not supported between instances of 'ListNode' and 'ListNode' +``` + +### 追記 +ListNodeに__lt__メソッドを強制的に加えた実装を追加 + +## 他の人のコード + +https://github.com/shining-ai/leetcode/pull/67 + +> これ (index) がないと、定義されていないListNodeの比較になって問題が出るのですかね。 + +- 非破壊的+heapを使わない解法: + +```python +lass Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + lists_val = [] + for node in lists: + while node: + lists_val.append(node.val) + node = node.next + lists_val.sort() + sentinel = ListNode() + node = sentinel + for i in lists_val: + node.next = ListNode(i) + node = node.next + + return sentinel.next +``` + +LinkedListの操作に気を取られて思いつかなかった。 + +時間計算量的はS=sum(lists[i].length)として O(Slog S)だが実際に実行してみると、heapの解法と大差なかった。 + + +> マージソートをイメージした解法 +> 先頭の2つのリストをマージしていき、最後の1つになるまで繰り返す + +自然な解法なので、面接で聞かれる可能性は高そう。これを思いつかなったのは、まだまだ修行が足りないということだろうな。 + +時間計算量 O(Slog k) + +sentinelという変数名は良いな + +```python + +class Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + def merge_two_lists(list_1, list_2): + sentinel = ListNode() + node = sentinel + while list_1 and list_2: + if list_1.val < list_2.val: + node.next = list_1 + list_1 = list_1.next + else: + node.next = list_2 + list_2 = list_2.next + node = node.next + if not list_1: + node.next = list_2 + else: + node.next = list_1 + return sentinel.next + + list_queue = deque(lists) + if not list_queue: + return None + while 1: + list_1 = list_queue.popleft() + if not list_queue: + return list_1 + list_2 = list_queue.popleft() + mearged_list = merge_two_lists(list_1, list_2) + list_queue.append(mearged_list) +``` + +## step3 +マージソートの解法を3回書くことにする。 + +merge_two_listsの内側でswapを用いることでsimpleになった気がする。 diff --git a/0023.Merge-k-Sorted-Lists/step1_in_place.py b/0023.Merge-k-Sorted-Lists/step1_in_place.py new file mode 100644 index 0000000..89a0246 --- /dev/null +++ b/0023.Merge-k-Sorted-Lists/step1_in_place.py @@ -0,0 +1,29 @@ +import heapq + + +# Definition for singly-linked list. +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +class Solution: + def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None: + heap_indices = [ + (node.val, i) for i, node in enumerate(lists) if node is not None + ] + heapq.heapify(heap_indices) + dummy = ListNode() + + node = dummy + while heap_indices: + _, i = heapq.heappop(heap_indices) + next_node = lists[i] + node.next = next_node + lists[i] = next_node.next + if next_node.next is not None: + heapq.heappush(heap_indices, (next_node.next.val, i)) + node = node.next + + return dummy.next diff --git a/0023.Merge-k-Sorted-Lists/step2_not_in_place.py b/0023.Merge-k-Sorted-Lists/step2_not_in_place.py new file mode 100644 index 0000000..38f3996 --- /dev/null +++ b/0023.Merge-k-Sorted-Lists/step2_not_in_place.py @@ -0,0 +1,26 @@ +import heapq + + +# Definition for singly-linked list. +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +class Solution: + def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None: + # Include index i to avoid comparison collisions when node values are equal. + heap = [(node.val, i, node) for i, node in enumerate(lists) if node is not None] + heapq.heapify(heap) + + dummy = ListNode() + node = dummy + while heap: + val, i, head = heapq.heappop(heap) + node.next = ListNode(val) + node = node.next + if head.next is not None: + heapq.heappush(heap, (head.next.val, i, head.next)) + + return dummy.next diff --git a/0023.Merge-k-Sorted-Lists/step2_not_in_place_lt.py b/0023.Merge-k-Sorted-Lists/step2_not_in_place_lt.py new file mode 100644 index 0000000..c09f6b4 --- /dev/null +++ b/0023.Merge-k-Sorted-Lists/step2_not_in_place_lt.py @@ -0,0 +1,27 @@ +import heapq + + +# Definition for singly-linked list. +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +class Solution: + def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None: + ListNode.__lt__ = lambda self, other: self.val < other.val + + heap = [node for node in lists if node is not None] + heapq.heapify(heap) + + dummy = ListNode() + node = dummy + while heap: + head = heapq.heappop(heap) + node.next = ListNode(head.val) + node = node.next + if head.next is not None: + heapq.heappush(heap, head.next) + + return dummy.next diff --git a/0023.Merge-k-Sorted-Lists/step3_merge.py b/0023.Merge-k-Sorted-Lists/step3_merge.py new file mode 100644 index 0000000..e1a7cae --- /dev/null +++ b/0023.Merge-k-Sorted-Lists/step3_merge.py @@ -0,0 +1,38 @@ +# Definition for singly-linked list. +import collections + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +class Solution: + def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None: + if not lists: + return None + + def merge_two_lists(node1, node2): + sentinel = ListNode() + node = sentinel + + while node1 is not None and node2 is not None: + if node1.val > node2.val: + node1, node2 = node2, node1 + node.next = node1 + node = node.next + node1 = node1.next + + node.next = node1 if node1 is not None else node2 + return sentinel.next + + node_to_merge = collections.deque(lists) + while 1: + node1 = node_to_merge.popleft() + + if not node_to_merge: + return node1 + + node2 = node_to_merge.popleft() + node_to_merge.append(merge_two_lists(node1, node2))