您的位置:首页 > 其它

【LeetCode】126. Word Ladder II

2016-09-07 18:43 387 查看
题目链接:
https://leetcode.com/problems/word-ladder-ii/
题目描述:

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord toendWord, such that:
Only one letter can be changed at a time
Each intermediate word must exist in the word list

For example,

Given:
beginWord = 
"hit"

endWord = 
"cog"

wordList = 
["hot","dot","dog","lot","log"]


Return

[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]

词阶是一个单词序列。在词阶中,所有单词的长度相等,相邻的单词只相差一个字母,而且这个序列长度最短。

求出beginWord到endWord的所有词阶。

涉及内容:

BFS(广度优先搜索),找出两点之间所有的最短路径

思考:

这是一个关于图搜索的问题。把每个单词当作一个节点,可以把相差一个字母的两个单词对应的节点连接起来,然后找出beginWord到endWord所有最短路径。

我们需要求最短路径,而且这个图是无向无权图,可以想到使用广度优先搜索算法(BFS)

一种方法是输入beginWord和endWord后,对单词逐位修改,并判断修改后的单词是否在词典中,进行广度优先搜索。

另一种方法是首先根据单词之间的关系,建立出对应的无向无权图,再在图中搜索。

这里谈论第二种方法。

如何快速地建立图呢?

得到一个单词的邻居,最简单的方法是逐位修改,判断修改后的单词是否存在,若存在,则它们之前存在一条边。

我想到了一种方法,如data和date,它们的最后一位是不相同的。除去最后一位后,剩下的都是dat.

为此可以这样储存:

边记录
str_edges[int 修改位编号][string 删掉修改位后的字符串] = [修改位的字符]
如data有:
str_edges[0]["ata"] = ['d',...]
str_edges[1]["dta"] = ['a',...]
str_edges[2]["dae"] = ['t',...]
str_edges[3]["dat"] = ['a',...]
若要获得单词data的邻居,则获取str_edges[0]["ata"], str_edges[1]["dta"], str_edges[2]["dae"], str_edges[3]["dat"]的值
获取邻居的时间复杂度为:nlog(n)


当建立图后,如何对图进行搜索,找出给定始末两点对应的所有最短路径呢?

首先思考如何得到一条最短路径

由于要搜索无权图的最短路径,可以想到使用BFS(广度优先搜索算法)

使用BFS,可以从出发点开始,由近到远搜索。

当检测到目标点时,需要回溯得到路径上的所有节点。

回溯的实现,可以使用两种方法:

方法1: 记录访问到的节点的上一个节点,回溯时延着上一个节点走即可。

方法2:在BFS的队列中加入一个栈结构,记录出发点到当前节点经过的所有节点,效率低,不推荐。

知道找一条最短路径的方法后,我们思考如何找到所有最短路径。

仿照回溯过程的方法1,可以在一个节点中,记录能一步到达这个节点的所有节点,如:

1 -> 2, 3 -> 2, 那么节点2记录{1, 3}

然后使用递归函数从目标节点到起点回溯,得到所有的最短路径。

参考代码:

string to_lower(string str){
string res;
for (int i = 0;i < str.size(); ++i){
char c = str[i];
if (c >= 'A' && c <= 'Z')c = c - 'A' + 'a';
res += c;
}
return res;
}

template <typename T>
void Shuffle(vector<T> &vs){
for (int i = 0;i < vs.size(); ++i){
int r = rand() % vs.size();
swap(vs[i], vs[r]);
}
}

template <typename T>
T gmin(T a, T b){
return a<b?a:b;
}

//处理所有长度为n的单词
class WordsNetwork{
public:
WordsNetwork(){}
//传入长度为n的单词组
WordsNetwork(vector<string> &vs){
//构建网络
BuildNetwork(vs);
}
//传入长度为n的两个单词,返回路径; 若返回值为空, 则路径不存在
//使用算法, BFS 广度优先搜索
vector<string> GetPath(string from, string to){
if (from.size() != len || to.size() != len)return vector<string>(); // 不合法
if (from == to)return vector<string>(1, from); // 对于同一节点
if (!sid.count(from) || !sid.count(to))return vector<string>(); // 不存在其中一个单词
vector<int> rec(sdata.size());
vector<bool> vis(sdata.size(), false);
int fid = sid[from];
int tid = sid[to];
queue<int> q; // 队列元素
q.push(fid);
rec[fid] = -1;
vis[fid] = true;
while(!q.empty()){
int id = q.front();
q.pop();
if (id == tid){
//到达目标点
stack<string> st;
while(id != -1){
st.push(sdata[id]);
id = rec[id]; // 取上一个节点
}
vector<string> path;
while(!st.empty()){
path.push_back(st.top());
st.pop();
}
return path;
}
// 没有达到目标点,添加邻居
for (int i = 0;i < edges[id].size(); ++i){
int nid = edges[id][i];
if (vis[nid])continue;
vis[nid] = true;
rec[nid] = id;
q.push(nid);
}
}
return vector<string>();
}

vector<vector<string> > GetPaths(string from, string to){
if (from.size() != len || to.size() != len)return vector<vector<string> >(); // 不合法
if (from == to)return vector<vector<string> >(1, vector<string>(1, from)); // 对于同一节点
if (!sid.count(from) || !sid.count(to))return vector<vector<string> >(); // 不存在其中一个单词
vector<bool> vis(sdata.size(), false);
vector<int> deep(sdata.size());
vector<set<int> > lastNode(sdata.size());
int fid = sid[from];
int tid = sid[to];
queue<pair<int, int> > q;
q.push(make_pair(fid, 0));
vis[fid] = true;
while(!q.empty()){
pair<int, int> p = q.front();
q.pop();
int id = p.first;
int distance = p.second;
deep[id] = distance;
if (id == tid){
continue;
}

// 没有达到目标点,添加邻居
for (int i = 0;i < edges[id].size(); ++i){
int nid = edges[id][i];
if (!vis[nid] || deep[nid] == distance + 1){
//id -> nid 正好距离为1
lastNode[nid].insert(id);
deep[nid] = distance + 1;
}
if (vis[nid])continue;
vis[nid] = true;
q.push(make_pair(nid, distance + 1));
}
}
//生成路径
vector<vector<string> > paths;
if (!lastNode[tid].empty()){
stack<int> st;
st.push(tid);
GeneratePaths(paths, lastNode, tid, st);
}
return paths;
}

void GeneratePaths(vector<vector<string> > &paths, const vector<set<int> > &lastNode, int tid, stack<int> st){
cout << tid << endl;
if (lastNode[tid].empty()){
//走到开头
vector<string> path;
while(!st.empty()){
path.push_back(sdata[st.top()]);
st.pop();
}
paths.push_back(path);
}else{
for (set<int>::const_iterator iter = lastNode[tid].begin(); iter != lastNode[tid].end(); ++iter){
cout << tid << "==" << *iter << endl;
stack<int> y = st;
y.push(*iter);
GeneratePaths(paths, lastNode, *iter, y);
}
}
}

private:
void BuildNetwork(vector<string> &vs){
//若vs为空
if (vs.empty())return;
//记下字符串
sdata = vs;
//由于传入单词组的长度一致,因此取第一个即可
len = vs[0].size();
str_edges.resize(len);
for (int i = 0;i < vs.size(); ++i){
string &word = vs[i];
sid[word] = i; // 记录id
for (int j = 0;j < len;++j){
string cut; // 删除第j位单词后的结果
for (int k = 0;k < len;++k){
if (j == k)continue;
cut += word[k];
}
str_edges[j][cut].insert(word[j]);
}
}
//建立id边
set<pair<int, int> > vis;  // 已经添加边
edges.resize(vs.size());
for (int i = 0;i < vs.size(); ++i){
string &word = vs[i];
int fid = sid[word];
for (int j = 0;j < len; ++j){
//改变第j位
string s;
for (int k = 0;k < len; ++k){
if (j != k)s += word[k];
}
set<char> &se = str_edges[j][s];
//构造邻居
for (set<char>::iterator iter = se.begin(); iter != se.end(); ++iter){
if (*iter == word[j])continue;
string neibor = word;
neibor[j] = *iter; // 改变字母
int tid = sid[neibor];
//fid -> tid
if (vis.count(make_pair(fid, tid)) || vis.count(make_pair(tid, fid)))continue; // 已添加
vis.insert(make_pair(fid, tid)); // 记录
edges[fid].push_back(tid);
edges[tid].push_back(fid);
}
}
}
}
private:
int len;
/*
边记录
str_edges[int 修改位编号][string 删掉修改位后的字符串] = [修改位的字符]
如data有:
str_edges[0]["ata"] = ['d',...]
str_edges[1]["dta"] = ['a',...]
str_edges[2]["dae"] = ['t',...]
str_edges[3]["dat"] = ['a',...]
若要获得单词data的邻居,则获取str_edges[0]["ata"], str_edges[1]["dta"], str_edges[2]["dae"], str_edges[3]["dat"]的值
获取邻居的时间复杂度为:nlog(n)
*/
vector<map<string, set<char> > > str_edges;
map<string, int> sid; // 从string映射到id
vector<vector<int> > edges; // 边
vector<string> sdata;
};

class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, unordered_set<string> &wordList) {
vector<string> vs;
for (auto w:wordList)vs.push_back(w);
WordsNetwork ns(vs);
return ns.GetPaths(beginWord, endWord);
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: