Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions 0023.Merge-k-Sorted-Lists/memo.md
Original file line number Diff line number Diff line change
@@ -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になった気がする。
29 changes: 29 additions & 0 deletions 0023.Merge-k-Sorted-Lists/step1_in_place.py
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions 0023.Merge-k-Sorted-Lists/step2_not_in_place.py
Original file line number Diff line number Diff line change
@@ -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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i は使われていないので不要そうです

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indexを格納しないと、valが等しい場合、< が定義されずエラーが起こります。
コメントなどで補おうと思います。

heapq.heapify(heap)

dummy = ListNode()
node = dummy
while heap:
val, i, head = heapq.heappop(heap)
node.next = ListNode(val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここを

node.next = ListNode(head.val)

とすればヒープにvalを持たせる必要がなくなり、heap の要素を単に node だけにできると思います

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここも、 heap の要素が単に node だけでは順序比較ができないので、このような実装にしました。

二点のご指摘を受けて、ListNodeに__lt__メソッドを強制的に加えた実装を追加しました。
ListNodeの実装を変更できる状況であればこの実装がより自然だと思います。

node = node.next
if head.next is not None:
heapq.heappush(heap, (head.next.val, i, head.next))

return dummy.next
27 changes: 27 additions & 0 deletions 0023.Merge-k-Sorted-Lists/step2_not_in_place_lt.py
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions 0023.Merge-k-Sorted-Lists/step3_merge.py
Original file line number Diff line number Diff line change
@@ -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))