タイトル説明
空でない文字列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思考
- メモリ検索なしのDFSメソッド
「leetcode」が破れる可能性があるかどうかは、次のように分けることができます。
- 「l」が単語リスト内の単語であるかどうか、および残りの部分文字列が壊れることがあるかどうか。
- 「le」が単語リスト内の単語であるかどうか、および残りの部分文字列が壊れることがあるかどうか。
- 「リー」...など...
DFSを使用してバックトラックし、可能なすべての分割を調べます。ポインターは左から右にスキャンします。
- ポインタの左側が単語の場合、残りの部分文字列は再帰的に調べられます。
- ポインタの左側が単語でない場合は、それを読まないで、戻って他のブランチを調べてください。
再帰ツリー、問題の解決策の空間ツリー
-
メモリ検索を使用したDFSメソッド
開始ポインタはノードの状態を表します。ご覧のとおり、多くの繰り返し計算が実行されています。
配列を使用して計算結果を格納します。配列のインデックスはポインタの位置であり、値は計算の結果です。次回同じサブ問題が発生した場合は、再帰を繰り返さずに、配列にキャッシュされた値を直接返します。
コード:
// 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が返されます。
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]
状態遷移方程式
- 次の図に示すように、ポインタjを使用してs [0:i]の部分文字列を除算します。
- s [0:i]の部分文字列のdp [i + 1]が真であるかどうか(単語に分割できるかどうか)は、次の2つの点に依存します。
- プレフィックス部分文字列s [0:j-1]のdp [j]が真であるかどうか。
- 残りの部分文字列s [j:i]が単一の単語であるかどうか。
規範事例
- 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];
}
画像参照:テキスト内の画像のソース