私は edit-distance 問題のこのバリエーションに出くわしました:
ソースワードをターゲットワードに変換するアルゴリズムを設計します。例:頭から尾まで、各ステップで1文字だけを置き換えることができ、Wordが有効である必要があります。辞書が与えられます。
これは明らかに 編集距離 問題のバリエーションですが、編集距離では、Wordが有効かどうかは気にしません。では、この要件を追加して距離を編集するにはどうすればよいですか。
これはグラフの問題としてモデル化できます。単語をグラフのノードと見なすことができ、2つのノードが接続されているのは、長さが同じで1つの文字が異なる場合のみです。
辞書を前処理してこのグラフを作成できます。次のようになります。
stack jack
| |
| |
smack back -- pack -- pick
次に、WordからWordを表すノードへのマッピングを作成できます。これには、ハッシュテーブル、高さバランスBSTを使用できます...
上記のマッピングを配置したら、2つのグラフノード間にパスが存在するかどうかを確認するだけです。これは、BFSまたはDFSを使用して簡単に実行できます。
したがって、アルゴリズムを次のように要約できます。
preprocess the dictionary and create the graph.
Given the two inputs words w1 and w2
if length(w1) != length(w2)
Not possible to convert
else
n1 = get_node(w1)
n2 = get_node(w2)
if(path_exists(n1,n2))
Possible and nodes in the path represent intermediary words
else
Not possible
codaddictのグラフアプローチは有効ですが、各グラフの作成にはO(n ^ 2)時間かかります。ここで、nは指定された長さの単語数です。それが問題である場合は、 bk-tree をより効率的に構築できます。これにより、対象の単語の特定の編集距離(この場合は1)を持つすべての単語を見つけることができます。
辞書の各ノードがWordを表すグラフを作成します。対応する単語の編集距離が1の場合、2つのWordノード間にエッジを追加します。必要な変換の最小数は、ソースノードと宛先ノード間の最短パスの長さになります。
これは編集距離ではないと思います。
これはグラフを使用して行うことができると思います。辞書からグラフを作成し、お気に入りのグラフトラバーサルアルゴリズムを使用して目的地までナビゲートしてみてください。
単純に再帰的なバックトラッキングを使用することもできますが、これは最適なソリューションとはかけ離れています。
# Given two words of equal length that are in a dictionary, write a method to transform one Word into another Word by changing only
# one letter at a time. The new Word you get in each step must be in the
# dictionary.
# def transform(english_words, start, end):
# transform(english_words, 'damp', 'like')
# ['damp', 'lamp', 'limp', 'Lime', 'like']
# ['damp', 'camp', 'came', 'lame', 'Lime', 'like']
def is_diff_one(str1, str2):
if len(str1) != len(str2):
return False
count = 0
for i in range(0, len(str1)):
if str1[i] != str2[i]:
count = count + 1
if count == 1:
return True
return False
potential_ans = []
def transform(english_words, start, end, count):
global potential_ans
if count == 0:
count = count + 1
potential_ans = [start]
if start == end:
print potential_ans
return potential_ans
for w in english_words:
if is_diff_one(w, start) and w not in potential_ans:
potential_ans.append(w)
transform(english_words, w, end, count)
potential_ans[:-1]
return None
english_words = set(['damp', 'camp', 'came', 'lame', 'Lime', 'like'])
transform(english_words, 'damp', 'lame', 0)
グラフやその他の複雑なデータ構造は必要ないと思います。私の考えは、辞書をHashSet
としてロードし、contains()
メソッドを使用して、Wordが辞書に存在するかどうかを調べることです。
これをチェックしてくださいpseudocode私の考えを見るには:
Two words are given: START and STOP.
//List is our "way" from words START to STOP, so, we add the original Word to it first.
list.add(START);
//Finish to change the Word when START equals STOP.
while(!START.equals(STOP))
//Change each letter at START to the letter to STOP one by one and check if such Word exists.
for (int i = 0, i<STOP.length, i++){
char temp = START[i];
START[i] = STOP[i];
//If the Word exists add a new Word to the list of results.
//And change another letter in the new Word with the next pass of the loop.
if dictionary.contains(START)
list.add(START)
//If the Word doesn't exist, leave it like it was and try to change another letter with the next pass of the loop.
else START[i] = temp;}
return list;
私が理解しているように、私のコードはそのように機能するはずです:
入力:DAMP、LIKE
出力:DAMP、LAMP、LIMP、Lime、LIKE
入力:戻る、PICK
出力:戻る、パック、ピック
これは、BFSを使用して問題を解決するC#コードです。
//use a hash set for a fast check if a Word is already in the dictionary
static HashSet<string> Dictionary = new HashSet<string>();
//dictionary used to find the parent in every node in the graph and to avoid traversing an already traversed node
static Dictionary<string, string> parents = new Dictionary<string, string>();
public static List<string> FindPath(List<string> input, string start, string end)
{
char[] allcharacters = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
foreach (string s in input)
Dictionary.Add(s);
List<string> currentFrontier = new List<string>();
List<string> nextFrontier = new List<string>();
currentFrontier.Add(start);
while (currentFrontier.Count > 0)
{
foreach (string s in currentFrontier)
{
for (int i = 0; i < s.Length; i++)
{
foreach (char c in allcharacters)
{
StringBuilder newWordBuilder = new StringBuilder(s);
newWordBuilder[i] = c;
string newWord = newWordBuilder.ToString();
if (Dictionary.Contains(newWord))
{
//avoid traversing a previously traversed node
if (!parents.Keys.Contains(newWord))
{
parents.Add(newWord.ToString(), s);
nextFrontier.Add(newWord);
}
}
if (newWord.ToString() == end)
{
return ExtractPath(start, end);
}
}
}
}
currentFrontier.Clear();
currentFrontier.Concat(nextFrontier);
nextFrontier.Clear();
}
throw new ArgumentException("The given dictionary cannot be used to get a path from start to end");
}
private static List<string> ExtractPath(string start,string end)
{
List<string> path = new List<string>();
string current = end;
path.Add(end);
while (current != start)
{
current = parents[current];
path.Add(current);
}
path.Reverse();
return path;
}