アルゴリズム概要

問題の説明

3つの文字列 s1s2s3 が与えられます。 s3s1s2interleaving(交互配置) で構成できるか判定します。

Interleaving とは、2つの文字列をそれぞれ部分文字列に分割し、元の順序を保ちながら交互に結合したものです。

入出力例

Example 1:

入力: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
出力: true
説明: s1 を "aa" + "bc" + "c" に分割し、s2 を "dbbc" + "a" に分割すると、
     "aa" + "dbbc" + "bc" + "a" + "c" = "aadbbcbcac" が得られる

Example 2:

入力: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
出力: false
説明: どのように交互配置してもs3を構成できない

制約条件

戦略の説明

主要ポイント

ステップバイステップ解説

Python実装

from __future__ import annotations
from typing import List


class Solution:
    """
    Interleaving String 判定クラス(LeetCode 用)

    Time Complexity:
        O(len(s1) * len(s2))

    Space Complexity:
        O(min(len(s1), len(s2)))  # 1次元DP
    """

    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        """
        s3 が s1 と s2 の interleaving で構成できるかどうかを判定する。

        Args:
            s1: 1つ目の文字列
            s2: 2つ目の文字列
            s3: 判定対象の文字列

        Returns:
            s3 が s1 と s2 の interleaving なら True、それ以外は False
        """
        n1: int = len(s1)
        n2: int = len(s2)
        n3: int = len(s3)

        # 長さが合わなければ不可能
        if n1 + n2 != n3:
            return False

        # dp の列方向(長さ)を常に「短い方の文字列」にする
        # → dp のサイズ縮小 + 内側ループ回数も減少
        if n2 > n1:
            # s1 を「長い方」、s2 を「短い方」に揃える
            s1, s2 = s2, s1
            n1, n2 = n2, n1

        # dp[j]: s1 の先頭 i 文字と s2 の先頭 j 文字で s3 の先頭 i+j 文字を作れるか
        dp: List[bool] = [False] * (n2 + 1)

        # i = 0 行(s1 を 0 文字使用)の初期化
        dp[0] = True
        for j in range(1, n2 + 1):
            dp[j] = dp[j - 1] and (s2[j - 1] == s3[j - 1])

        # i >= 1 行の更新
        for i in range(1, n1 + 1):
            # j = 0 列(s2 を 0 文字使用)の更新
            dp[0] = dp[0] and (s1[i - 1] == s3[i - 1])

            for j in range(1, n2 + 1):
                k: int = i + j - 1  # s3 のインデックス

                # 上から来る: s1 の文字を使う
                from_s1: bool = dp[j] and (s1[i - 1] == s3[k])
                # 左から来る: s2 の文字を使う
                from_s2: bool = dp[j - 1] and (s2[j - 1] == s3[k])

                dp[j] = from_s1 or from_s2

        return dp[n2]

フローチャート

開始 n1 + n2 == n3? いいえ False 返却 はい n2 > n1? はい s1, s2 を swap n1, n2 を swap いいえ dp 配列初期化 dp[0] = True i=0 行の初期化 j=1..n2 をループ i=1..n1 をループ j=0 列を更新 j=1..n2 をループ k = i+j-1 計算 from_s1 = dp[j] and s1[i-1]==s3[k] from_s2 = dp[j-1] and s2[j-1]==s3[k] dp[j] = from_s1 or from_s2 次の j 次の i dp[n2] 返却 終了

フローの説明:
1. まず n1 + n2 == n3 をチェック(不一致なら即 False)
2. n2 > n1 なら swap して、短い方を列方向に配置
3. dp 配列を初期化し、i=0 行(s2 のみ使用)を設定
4. i=1..n1 の各行で、j=0 列を更新後、j=1..n2 をループ
5. 各 dp[j] は「s1 から遷移」または「s2 から遷移」の論理和
6. すべてのループ完了後、dp[n2] を返却

計算量分析

時間計算量

O(len(s1) × len(s2))

空間計算量

O(min(len(s1), len(s2)))

手法比較表

手法 時間計算量 空間計算量 実装難度 備考
再帰 DFS + メモ化 O(n1×n2) O(n1×n2) スタック深度に注意
2D DP O(n1×n2) O(n1×n2) 最も分かりやすい
1D DP(本実装) O(n1×n2) O(min(n1,n2)) 空間効率最適、Follow-up対応