トピック
説明
小文字とターゲット文字列のみを含む文字列のセットを指定して、ターゲット文字列を取得するためにk回以内に操作できるすべての文字列を出力します。
文字列に対して次の3つの操作を実行できます。
1文字追加
1文字削除
1文字を置き換えます
サンプル
例1:
与えられた文字列["abc","abd","abcd","adc"]
、ターゲット文字列は"ac"
、k = 1
return["abc","adc"]
入力:
["abc"、 "abd"、 "abcd"、 "adc"]
"ac"
1
出力:
["abc"、 "adc"]
説明:
" abc"は"b"を削除
します"adc"は"d"を削除します
例2:
入力:
["acc"、 "abcd"、 "ade"、 "abbcd"]
"abc"
2
出力:
["acc"、 "abcd"、 "ade"、 "abbcd"]
説明:
"acc" 「c」を「b」に変更
「abcd」ドロップ「d」
「ade」「d」を「b」に変更「e」を「c」に変更
「abbcd」ドロップ「b」と「d」
分析する
前の記事のサンプルの80%の方法を続けて、タイムアウトの理由を分析する必要があります。前の方法に従って、abc、abdなどの繰り返し操作は何ですか。各文字が1つ必要です。計算結果を比較すると、実際にはabを2回計算しました。データ量が多いと繰り返し回数がひどくなりますが、このときabの値を格納すると次のようになります。次回使用時に直接取り出したので、辞書ツリー(プレフィックスツリー)を使用します。
前回の記事に辞書ツリーの説明がありますが、辞書ツリーを事前に理解しておくと、この質問を見やすくなることをお勧めします。
コードセクション
1.辞書ツリーと関数の設計
ここでは、最初にすべての文字列を辞書ツリーに構築します。ルートノードが必要です。小文字しかないため、各ノードにはその息子を格納するための26個のオブジェクトの配列が必要です。したがって、26であり、ここでは2つを直接使用します。次元配列で表され、配列自体の値がその数を表します。0の場合は、そのような文字がないことを意味します。
1次元配列は、ノードの位置に単語があるかどうかを表します。たとえば、abcはcでマークする必要のある単語です。そうでない場合、すべての文字列も単語になり、判断できません。
1次元の文字列配列で、単語を単語の位置に保存すると、条件が満たされた場合に単語を回答に直接格納し、
各文字列を挿入するときに各文字を挿入できます。文字のパスが追加され、次にノードに番号が付けられ、最後に文字列の最後のノードがマークされ、ノードが単語であることを示し、この時点での単語が記録されます。
const int MAX_NODE=1000000; //总结点最大数量
const int CHARSET=26; //字典树且都为小写字母
int trie[MAX_NODE][CHARSET]={
0}; //0表示该节点没被添加过
int color[MAX_NODE]={
0}; //对每个节点进行标记 1为叶子节点
vector<string> nowstr(MAX_NODE); //存放叶子节点单词
int K=1; //记录节点个数以及标记当前节点的
void insert(string w) //增加节点
{
int len=w.length();
int p=0;
for(int i=0;i<len;i++)
{
int c=w[i]-'a'; //字母转换成数字
if(!trie[p][c]) //如果当前节点没有到该节点的路径就添加
{
trie[p][c]=K;
++K;
}
p=trie[p][c]; //遍历下一个
}
nowstr[p]=w;
color[p]=1; //标记叶子节点
}
2.状態と辞書ツリーを初期化します
すべての文字列を辞書ツリーに1つずつ挿入して
状態にします。ここで作成する状態は1次元です。これは、辞書ツリーの各ノードをdfsして適切なノードを選択する必要があるため、現在の状態がターゲットを表すためです。文字列のノード、ここで疑問があるかもしれません:他の次元の状態はどこに行きますか?、dfsは各ノード(文字)をトラバースするため、別の次元の状態がdfsの各ステップに含まれ、他の次元は深くなるたびに1つずつ追加されます。
int n=target.size();
vector<string> ans;
for(int i=0;i<words.size();i++)
{
insert(words[i]);
}
vector<int> f(n+1);
for(int i=0;i<=n;i++) //初始化目标字符串当前为空的情况
f[i]=i;
3.dfsで各ノードのステータスを確認します
ここでは、dfsで新しい状態を作成する必要があります。この状態は現在の状態を表し、渡す状態は前の状態を表し、ループして現在のノードに存在する子ノードを見つけ、その状態を計算し、dfsは深く検索します。現在のノードを判断するための次の
dfsの開始。ノードに単語があるとマークされ、状態の値がk以下の場合、その単語は回答に格納されます。dfsが全体を深く検索すると辞書ツリー、答えは完全です
void dfs(int i,vector<int> f,vector<string> &ans,
int n,int k,string target)
{
vector<int> nf(n+1); //当前状态 f为上一个状态
if(color[i])
{
if(f[n]<=k)
ans.push_back(nowstr[i]);
}
for(int j=0;j<26;++j)
{
if(trie[i][j]==0)
continue;
nf[0]=f[0]+1; //当目标字符串长度为0
for(int k=1;k<=n;k++)
{
nf[k]=min(min(nf[k-1]+1,f[k]+1),f[k-1]+1);
int c=target[k-1]-'a';
if(c==j)
nf[k]=min(nf[k],f[k-1]);
}
dfs(trie[i][j],nf,ans,n,k,target);
}
}
完全なコード
const int MAX_NODE=1000000; //总结点最大数量
const int CHARSET=26; //字典树且都为小写字母
int trie[MAX_NODE][CHARSET]={
0}; //0表示该节点没被添加过
int color[MAX_NODE]={
0}; //对每个节点进行标记 1为叶子节点
vector<string> nowstr(MAX_NODE); //存放叶子节点单词
int K=1; //记录节点个数以及标记当前节点的
class Solution {
public:
void insert(string w) //增加节点
{
int len=w.length();
int p=0;
for(int i=0;i<len;i++)
{
int c=w[i]-'a'; //字母转换成数字
if(!trie[p][c]) //如果当前节点没有到该节点的路径就添加
{
trie[p][c]=K;
++K;
}
p=trie[p][c]; //遍历下一个
}
nowstr[p]=w;
color[p]=1; //标记叶子节点
}
void dfs(int i,vector<int> f,vector<string> &ans,
int n,int k,string target)
{
vector<int> nf(n+1); //当前状态 f为上一个状态
if(color[i])
{
if(f[n]<=k)
ans.push_back(nowstr[i]);
}
for(int j=0;j<26;++j)
{
if(trie[i][j]==0)
continue;
nf[0]=f[0]+1; //当目标字符串长度为0
for(int k=1;k<=n;k++)
{
nf[k]=min(min(nf[k-1]+1,f[k]+1),f[k-1]+1);
int c=target[k-1]-'a';
if(c==j)
nf[k]=min(nf[k],f[k-1]);
}
dfs(trie[i][j],nf,ans,n,k,target);
}
}
vector<string> kDistance(vector<string> &words, string &target, int k) {
int n=target.size();
vector<string> ans;
for(int i=0;i<words.size();i++)
{
insert(words[i]);
}
vector<int> f(n+1);
for(int i=0;i<=n;i++) //初始化目标字符串当前为空的情况
f[i]=i;
dfs(0,f,ans,n,k,target);
return ans;
}
};
要約する
この質問は、辞書ツリーのない二重シーケンス動的計画法です。辞書ツリーを使用すると、ツリー型の動的計画法になります。辞書ツリーに触れていない場合、この質問は理解しにくく、私も聞いています。ホウ・ウェイドン氏がこの問題を説明した後、辞書ツリーのようなものがあることに気づき、再び辞書ツリーを学びました。この問題のコードを実装したとき、彼はまだつまずきました。彼はホウさんがこれを何度も練習したと言ったのを聞いてほっとしました