【LeetCode】126. Word Ladder II

题目链接:

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:

  1. Only one letter can be changed at a time
  2. 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);
    }
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值