[動的計画法] LeetCode-139。ワードスプリット-DFS、BFS、DP

139.単語分割

タイトル説明

空でない文字列sと空でない単語を含むリストwordDictが与えられた場合、sをスペースで分割して辞書に表示される1つ以上の単語にすることができるかどうかを判断します。

説明:

辞書内の単語は、分割時に再利用できます。
辞書には繰り返し単語がないと想定できます。
例1:

入力:s = "leetcode"、wordDict = ["leet"、 "code"]
出力:true
説明:「leetcode」は「leetcode」に分割できるため、trueを返します。
例2:

入力:s = "applepenapple"、wordDict = ["apple"、 "pen"]
出力:true
説明:「applepenapple」は「applepenapple」に分割できるため、trueを返します。
辞書内の単語を再利用できることに注意してください。
例3:

入力:s =“ catsandog”、wordDict = [“ cats”、“ dog”、“ sand”、“ and”、“ cat”]
出力:false

問題解決のアイデア

(1)DFS思考

  1. メモリ検索なしのDFSメソッド
  • 「leetcode」が破れる可能性があるかどうかは、次のように分けることができます。

    • 「l」が単語リスト内の単語であるかどうか、および残りの部分文字列が壊れることがあるかどうか。
    • 「le」が単語リスト内の単語であるかどうか、および残りの部分文字列が壊れることがあるかどうか。
    • 「リー」...など...
  • DFSを使用してバックトラックし、可能なすべての分割を調べます。ポインターは左から右にスキャンします。

    • ポインタの左側が単語の場合、残りの部分文字列は再帰的に調べられます。
    • ポインタの左側が単語でない場合は、それを読まないで、戻って他のブランチを調べてください。

再帰ツリー、問題の解決策の空間ツリー

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-higVA3tm-1616766905069)(D:\ Pictures \ labuladong_img \ 1-3_1.png )]

  1. メモリ検索を使用したDFSメソッド

    開始ポインタはノードの状態を表します。ご覧のとおり、多くの繰り返し計算が実行されています。

    [外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-NV5OUjSi-1616766905073)(D:\ Pictures \ labuladong_img \ 1-3_2.png )]

    配列を使用して計算結果を格納します。配列のインデックスはポインタの位置であり、値は計算の結果です。次回同じサブ問題が発生した場合は、再帰を繰り返さずに、配列にキャッシュされた値を直接返します。

    [外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-zSEBO6lO-1616766905075)(D:\ Pictures \ labuladong_img \ 1-3_3.png )]

コード:

// BFS
    public boolean wordBreak(String s, List<String> wordDict) {
    
    
        Queue<Integer> queue = new LinkedList<>();
        queue.add(0);

        int slength = s.length();
        boolean[] visited = new boolean[slength + 1];

        while (!queue.isEmpty()) {
    
    
            int size = queue.size();
            for (int i = 0; i < size; i++) {
    
    
                int start = queue.poll().intValue();
                for (String word : wordDict) {
    
    
                    int nextStart = start + word.length();
                    if (nextStart > slength || visited[nextStart]) {
    
    
                        continue;
                    }

                    if (s.indexOf(word, start) == start) {
    
    
                        if (nextStart == slength) {
    
    
                            return true;
                        }

                        queue.add(nextStart);
                        visited[nextStart] = true;
                    }
                }
            }
        }

        return false;
    }

(2)BFS

1.重複ノードにアクセスできるBFS

  • ちょうど今、DFSを使用してスペースツリーをトラバースしました。もちろん、BFSも使用できます。
  • キューを維持し、ポインターを使用してノードを記述し、ポインターを確認します。
  • 最初に、ポインタ0が列に入り、次に列から出ます。ポインタ1、2、3、4、5、6、7、8はその子ノードであり、プレフィックス部分文字列はそれぞれ0で囲まれています。それは単語ではなく、対応するポインタリストに入力しないでください。そうでない場合はリストに入力して、そこから始まる残りの部分文字列の調査を続けてください。
  • 次に、繰り返します。ノード(ポインター)がデキューされ、その子ノードが検査され、入力可能なノードが再びキューに入れられ、キューから取り出されます。
  • ポインタが境界を越えるまで、残りの部分文字列はなく、ポインタを入力することはできません。プレフィックス部分文字列が単語の場合、その単語は以前に切り取られていることを意味し、trueが返されます。
  • BFSプロセス全体がtrueを返さない場合は、falseが返されます。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-Y68BEiIZ-1616766905077)(D:\ Pictures \ labuladong_img \ 1-3_4.png )]

2.BFSは重複ノードへのアクセスを回避します

プルーニングされていないDFSはノードを繰り返しトラバースしますが、BFSについても同じことが言えます。タイムアウトの場合、BFSがノードに繰り返しアクセスする方法を考えてみましょう。

解決策:visited配列を使用して、visitedノードを記録します。ポインターを検査するために外出するときは、visitedに存在する場合はスキップし、存在しない場合はvisitedに保存します。

コード:

  // BFS
    public boolean wordBreak(String s, List<String> wordDict) {
    
    
        Queue<Integer> queue = new LinkedList<>();
        queue.add(0);

        int slength = s.length();
        boolean[] visited = new boolean[slength + 1];

        while (!queue.isEmpty()) {
    
    
            int size = queue.size();
            for (int i = 0; i < size; i++) {
    
    
                int start = queue.poll().intValue();
                for (String word : wordDict) {
    
    
                    int nextStart = start + word.length();
                    if (nextStart > slength || visited[nextStart]) {
    
    
                        continue;
                    }

                    if (s.indexOf(word, start) == start) {
    
    
                        if (nextStart == slength) {
    
    
                            return true;
                        }

                        queue.add(nextStart);
                        visited[nextStart] = true;
                    }
                }
            }
        }

        return false;
    }

(3)動的計画

  • s文字列を語彙内の単語に分解できますか?つまり、最初のs.length文字列を語彙内の単語に分解できるかどうか。
  • 大きな問題は小さなサブ問題に分解されます。スケールの違いは長さです。問題は次のように分解されます。
    • 最初のi文字の部分文字列を単語に分解できますか
    • 残りの部分文字列が単一の単語であるかどうか。
  • dp [i]:長さiのs [0:i-1]部分文字列を単語に分割できるかどうか。タイトルは私達に尋ねることを要求します:dp [s.length]

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-88G8oIIt-1616766993347)(D:\ Pictures \ labuladong_img \ 1-3_5.png )]

状態遷移方程式

  • 次の図に示すように、ポインタjを使用してs [0:i]の部分文字列を除算します。
  • s [0:i]の部分文字列のdp [i + 1]が真であるかどうか(単語に分割できるかどうか)は、次の2つの点に依存します。
    • プレフィックス部分文字列s [0:j-1]のdp [j]が真であるかどうか。
    • 残りの部分文字列s [j:i]が単一の単語であるかどうか。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-Q0JVVZ9k-1616766993348)(D:\ Pictures \ labuladong_img \ 1-3_6.png )]

規範事例

  • dp [0] = true。0 s [0:-1]の長さは、単語リストの単語に分割できます。
  • ばかげているように見えますが、これは境界条件が状態遷移方程式も満たすようにするためです。
  • j = 0の場合(上の図の黄色の部分は空の文字列であり、プレフィックス文字列をjで割ったものは空の文字列です)、s [0:i]の部分文字列のdp [i +1]はs [0:-1] dp [0]の値、および残りの部分文字列s [0:i]が単一の単語であるかどうか。
  • dp [0]が真の場合にのみ、dp [i + 1]は、s [0:i]が単一の単語であり、状態遷移方程式を満たすかどうかにのみ依存します。

最適化後の動的計画法

  • 反復プロセスで、dp [i] == trueが見つかった場合は、直接中断します
  • dp [j] == falseの場合、dp [i]が真になる可能性はなく、続行して次のjを調べます。

コード:

// DP
    public boolean wordBreak(String s, List<String> wordDict) {
    
    
        int maxWordLength = 0;
        Set<String> wordSet = new HashSet<>(wordDict.size());
        for (String word : wordDict) {
    
    
            wordSet.add(word);

            if (word.length() > maxWordLength) {
    
    
                maxWordLength = word.length();
            }
        }

        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i < dp.length; i++) {
    
    
            for (int j = (i - maxWordLength < 0 ? 0 : i - maxWordLength); j < i; j++) {
    
    
                if (dp[j] && wordSet.contains(s.substring(j, i))) {
    
    
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[dp.length - 1];
    }

画像参照:テキスト内の画像のソース

おすすめ

転載: blog.csdn.net/qq_35655602/article/details/115256164