2 本ずつまとめて k 本の 並んだ 連結リストを 1 本にする
小さい
海外で定番の問題リスト 4 本には入っていません
問題
小さいlists を
- 各節は
val(値) とnext(次の節) を 持ちます。 最後の 節の nextは、Python では None、JavaScript では null、Go では nilです。Go の 節は ListNodeで、Val、Nextと大文字で 始まります。 Go の listsは[]*ListNodeです - どの
リストも 小さい 順に 並んでいます。 同じ 値が 何度出てもかまいません listsは空の ことも あり、 中に 空の リストが 混じる こともあります。 節が 1 つも 無ければ 空のまま返します - k は 0 以上 100 以下、
それぞれの 節の 数は 0 以上 50 以下、 値は どれも -1000 以上 1000 以下です
例では、lists は、
入力と出力の例
例 1
3 本の
例 2
空の
例 3
4 本の
面接での聞かれ方
2 本を
- 「並んだ
連結リストが k 本あります。 1 本の 並んだリストに まとめてください」 - 「ログを
出すサーバーが k 台 あり、 どれも 時刻の 順に 並んだ 記録を 持っています。 全部を 時刻の 順に 1 本に してください」と、 場面を 付けて 聞かれる こともあります - 書き終えた
あとに 「優先度付きの 待ち行列 (ヒープ) でも 書けますか?」 「k が 1 万、 節の 合計が 1 億なら どちらを 選びますか?」と、 もう 1 つの 書き方と 比べさせられます
シニアの
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「配列と文字列」に、
関わる学び
配列と
まず 考える手順
段 1
まずは
段 2
足す順を
段 3
本数が
段 4
2 本をlists が
解答
Python
Python
def merge_two(a, b):
dummy = ListNode(0)
tail = dummy
while a and b:
if a.val <= b.val:
tail.next = a
a = a.next
else:
tail.next = b
b = b.next
tail = tail.next
tail.next = a if a else b
return dummy.next
def merge_k_lists(lists):
if not lists:
return None
while len(lists) > 1:
n = len(lists)
merged = []
for i in range(0, n, 2):
a = lists[i]
b = None
if i + 1 < n:
b = lists[i + 1]
merged.append(
merge_two(a, b)
)
lists = merged
return lists[0]JavaScript
JavaScript
function mergeTwo(a, b) {
const dummy = new ListNode(0);
let tail = dummy;
while (a !== null
&& b !== null) {
if (a.val <= b.val) {
tail.next = a;
a = a.next;
} else {
tail.next = b;
b = b.next;
}
tail = tail.next;
}
tail.next = a ?? b;
return dummy.next;
}
function mergeKLists(lists) {
if (lists.length === 0) {
return null;
}
while (lists.length > 1) {
const n = lists.length;
const merged = [];
for (let i = 0; i < n;) {
const b = i + 1 < n
? lists[i + 1]
: null;
merged.push(
mergeTwo(lists[i], b),
);
i += 2;
}
lists = merged;
}
return lists[0];
}Go
Go
func mergeTwo(
a, b *ListNode,
) *ListNode {
dummy := &ListNode{}
tail := dummy
for a != nil && b != nil {
if a.Val <= b.Val {
tail.Next = a
a = a.Next
} else {
tail.Next = b
b = b.Next
}
tail = tail.Next
}
if a != nil {
tail.Next = a
} else {
tail.Next = b
}
return dummy.Next
}
func mergeKLists(
lists []*ListNode,
) *ListNode {
if len(lists) == 0 {
return nil
}
for len(lists) > 1 {
n := len(lists)
merged := []*ListNode{}
for i := 0; i < n; i += 2 {
var b *ListNode
if i+1 < n {
b = lists[i+1]
}
merged = append(merged,
mergeTwo(lists[i], b))
}
lists = merged
}
return lists[0]
}merged は 1 回のb が
計算量
- 時間は O(N log k) です。
N は 全部の 節の 数、 k は リストの 本数です。 1 回の 組み合わせで どの 節も 1 度ずつ動いて O(N)、 本数が 半分ずつ減るので 組み合わせは log k 回です - 使う
記憶は O(k) です。 節は つなぎ直すだけで 新しく 作らず、 1 回ごとに まとめた リストの 先頭を 入れる 並び mergedだけを持ちます。 ヒープで 書いても、 k 本の 先頭を 入れておくので 同じ O(k) です
よく ある間違い
- 1 本ずつ順に
足していく。 答えは 合いますが、 まとめた 長いリストを 毎回なめ直すので O(N k) です。 k が 大きいと 間に 合わず、 面接では 「もっと速く」と 必ず 言われます - 全部の
値を 並びに 集めて 並べ替え、 節を 作り直す。 O(N log N) で 動きますが、 もとから 並んでいるという 手が かりを 使っていません。 節を 作り直すので 記憶も O(N) 要ります - 本数が
奇数の ときに 最後の 1 本を落とす。 i + 1が範囲の 外に なる所で 止めると、 例 1 の 3 本目 (4、 8、 20) が 消えます。 相手が 無い ときは 空と まとめて残します listsが空の ときに lists[0]を読む。 0 本なら、まとめる 前に 空を 返します。 中に 空の リストが あるだけなら、 2 本を まとめる 所が そのまま扱えます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「1 本ずつ順に
足すと、 長くなった リストを 毎回なめ直すので O(N k) です。 代わりに 隣どうしを 2 本ずつまとめます。 1 回で 本数が 半分に なり、 どの 節も 1 回に 1 度しか 動かないので、 全体で O(N log k) です。 2 本を まとめる 所は ダミーの 節を 使う、 いつもの 書き方です。 本数が 奇数なら、 余った 1 本は 空と まとめて 次へ 持ち越します。」
書き終えたら、
「例 3 で
たどります。 1 回目は、 [-5, 0] と [-5] から [-5, -5, 0]、 [2, 2, 7] と [-1] から [-1, 2, 2, 7] が できます。 2 回目に この 2 本を まとめて、 -5、 -5、 -1、 0、 2、 2、 7 です。 ヒープで 書くなら、 k 本の 先頭を 入れておき、 いちばん 小さい 物を 取り出してつなぎ、 その次の 節を 入れ直します。 こちらも O(N log k) です。」