剑指 Offer II 114. 外星文字典
现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。
给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。
请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。
字符串 s 字典顺序小于 字符串 t 有两种情况:
在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
示例 1:
输入:words = ["wrt","wrf","er","ett","rftt"]
输出:"wertf"
示例 2:
输入:words = ["z","x"]
输出:"zx"
示例 3:
输入:words = ["z","x","z"]
输出:""
解释:不存在合法字母顺序,因此返回 "" 。
提示:
1 <= words.length <= 100
1 <= words[i].length <= 100
words[i] 仅由小写英文字母组成
给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。
请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。
字符串 s 字典顺序小于 字符串 t 有两种情况:
在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
示例 1:
输入:words = ["wrt","wrf","er","ett","rftt"]
输出:"wertf"
示例 2:
输入:words = ["z","x"]
输出:"zx"
示例 3:
输入:words = ["z","x","z"]
输出:""
解释:不存在合法字母顺序,因此返回 "" 。
提示:
1 <= words.length <= 100
1 <= words[i].length <= 100
words[i] 仅由小写英文字母组成
题解:
这道题的思想非常经典,将这些外星文建立一个有向图,然后再采用拓扑排序进行验证。如果存在环,那么就不存在合法的字符顺序。
这个深度优先搜索的方式我没有想出来,但是思想大体是差不多的,只不过拓扑排序是一个逆向的方式,需要将下标反转一下。
class Solution { public: const int VISITING = 1, VISITED = 2; unordered_map<char, vector<char>> edges; unordered_map<char, int> states; bool valid = true; string order; int index; string alienOrder(vector<string>& words) { int length = words.size(); for (string & word : words) { int wordLength = word.size(); for (int j = 0; j < wordLength; j++) { char c = word[j]; if (!edges.count(c)) { edges[c] = vector<char>(); } } } for (int i = 1; i < length && valid; i++) { addEdge(words[i - 1], words[i]); } order = string(edges.size(), ' '); index = edges.size() - 1; for (auto [u, _] : edges) { if (!states.count(u)) { dfs(u); } } if (!valid) { return ""; } return order; } void addEdge(string before, string after) { int length1 = before.size(), length2 = after.size(); int length = min(length1, length2); int index = 0; while (index < length) { char c1 = before[index], c2 = after[index]; if (c1 != c2) { edges[c1].emplace_back(c2); break; } index++; } if (index == length && length1 > length2) { valid = false; } } void dfs(char u) { states[u] = VISITING; for (char v : edges[u]) { if (!states.count(v)) { dfs(v); if (!valid) { return; } } else if (states[v] == VISITING) { valid = false; return; } } states[u] = VISITED; order[index] = u; index--; } };还有一种方法,是使用广度优先搜索,这个方案会更加直观,我平时对拓扑排序的了解,也是采用这种方法解决的,产生的是一个正向的序列,先将顶点的入度求出来。
class Solution { public: unordered_map<char, vector<char>> edges; unordered_map<char, int> indegrees; bool valid = true; string alienOrder(vector<string>& words) { //对存在的字母初始化一个图 int length = words.size(); for(auto word: words){ for(int j = 0; j < word.size(); j++){ char ch = word[j]; if(!edges.count(ch)){ edges[ch] = vector<char>(); } } } //给图插入边 for(int i = 1; i < length && valid; i++){ addEdge(words[i - 1], words[i]); } if(!valid) return ""; //拓扑排序 //将入度为0的节点入队 queue<char> q; for(auto [u, _]: edges){ if(indegrees[u] == 0){ q.push(u); } } //维护队列,将当前取出的节点邻居入度减1,如果此时入度变为0,则入队 string order; while(!q.empty()){ char u = q.front(); q.pop(); order.push_back(u); for(auto v: edges[u]){ indegrees[v]--; if(indegrees[v] == 0){ q.push(v); } } } return order.size() == edges.size()? order: ""; } //将字母之间连成一个图 void addEdge(string before, string after){ int len1 = before.size(); int len2 = after.size(); int len = min(len1, len2); int index = 0; while(index < len){ char c1 = before[index], c2 = after[index]; if(c1 != c2){ edges[c1].push_back(c2); //c2为入边,需要更新入度 indegrees[c2]++; break; } index++; } //出现了环,或者相同的字符串 if(index == len && len1 > len2){ valid = false; } } };