アルゴリズム概要
ソート済み連結リストの特性を活かし、ダミーノードと2つのポインタを使用して重複要素を効率的に削除する手法です。
問題の要求事項
- ソート済み連結リストが入力として与えられる
- 重複する値を持つノードはすべて削除する
- 一意な値を持つノードのみを残す
- 結果もソート済みの状態を維持する
解法の核心
- ダミーノード: 先頭削除の場合も統一的に処理
- Two Pointers: prev(安全な位置)とcurr(探索位置)
- 重複検出: curr.val == curr.next.valで判定
- スキップ処理: 同値ノードをすべて飛ばす
アルゴリズムの特徴
- 時間計算量: O(n) - 各ノードを一度だけ走査
- 空間計算量: O(1) - 追加のデータ構造不要
- 安定性: ソート順を維持
- 効率性: インプレース操作
ステップバイステップ解説
処理を簡潔にするためダミーノードを作成し、元のheadに接続します。prevポインタをダミーノードに設定。
currノードの値とcurr.nextノードの値を比較し、重複があるかチェックします。
重複が見つかった場合、同じ値を持つすべてのノードをスキップしてcurrを進めます。
prev.nextをcurrに設定し、重複ノードを除外したリンクを構築します。
重複がない場合はprevとcurrの両方を次に進め、重複がある場合はcurrのみ進めます。
実行例:[1,2,3,3,4,4,5] → [1,2,5]
実装コード
from typing import Optional
class ListNode:
def __init__(self, val: int = 0, next: Optional["ListNode"] = None) -> None:
self.val: int = val
self.next: Optional["ListNode"] = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
Time Complexity: O(n) - 各ノードを一度だけ走査
Space Complexity: O(1) - ポインタ操作のみ
"""
dummy = ListNode(0, head) # ダミーノード作成
prev = dummy # 最後に確定したユニーク要素の直前
curr = head
while curr:
# 重複チェック
if curr.next and curr.val == curr.next.val:
# 重複値を保存
dup_val = curr.val
# 同じ値のノードをすべてスキップ
while curr and curr.val == dup_val:
curr = curr.next
# 重複ノードを飛ばして接続
prev.next = curr
else:
# 重複なしの場合、ポインタを進める
prev = curr
curr = curr.next
return dummy.next
from typing import Optional
class ListNode:
def __init__(self, val: int = 0, next: Optional["ListNode"] = None) -> None:
self.val: int = val
self.next: Optional["ListNode"] = next
class Solution:
def deleteDuplicates_safe(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
型安全・エラーハンドリング付きの堅牢版
Args:
head: 連結リストの先頭ノード
Returns:
重複を除去した連結リストの先頭ノード
Raises:
TypeError: headがListNodeまたはNoneでない場合
"""
# 入力検証
if head is not None and not isinstance(head, ListNode):
raise TypeError("head must be a ListNode or None")
# エッジケース処理
if head is None or head.next is None:
return head
dummy = ListNode(0, head)
prev = dummy
curr = head
while curr:
if curr.next and curr.val == curr.next.val:
dup_val = curr.val
# 重複ノードを安全にスキップ
while curr and curr.val == dup_val:
curr = curr.next
prev.next = curr
else:
prev = curr
curr = curr.next
return dummy.next
計算量解析
各ノードを最大で一度だけ走査するため、線形時間で処理が完了します。
ダミーノードと数個のポインタ変数のみ使用。入力サイズに依存しない定数空間。
パフォーマンス特性
- 最良の場合: 重複なし → O(n)時間で全ノードを一度走査
- 最悪の場合: 全て重複 → O(n)時間で全ノードを削除
- 平均の場合: 部分的重複 → O(n)時間で効率的処理
- メモリ効率: インプレース操作でメモリ使用量を最小化