现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。
给定一个字符串列表 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”]
输出:“”
解释:不存在合法字母顺序,因此返回 “” 。
这题其实还是拓扑排序的问题,拓扑排序问题就是解决有向图是否有环的.如果没有环则就是拓扑排序,否则就不是拓扑排序.同样,做这类题的思路就是:
使用深度优先搜索实现拓扑排序的总体思想是:对于一个特定节点,如果该节点的所有相邻节点都已经搜索完成,则该节点也会变成已经搜索完成的节点,在拓扑排序中,该节点位于其所有相邻节点的前面。一个节点的相邻节点指的是从该节点出发通过一条有向边可以到达的节点。
由于拓扑排序的顺序和搜索完成的顺序相反,因此需要使用一个栈存储所有已经搜索完成的节点。深度优先搜索的过程中需要维护每个节点的状态,每个节点的状态可能有三种情况:「未访问」、「访问中」和「已访问」。初始时所有节点的状态都是「未访问」。
每一轮搜索时,任意选取一个「未访问」的节点u,从节点 u 开始深度优先搜索。将节点 u 的状态更新为「访问中」,对于每个与节点 u 相邻的节点 v,判断节点 v 的状态,执行如下操作:
如果节点 v 的状态是「未访问」,则继续搜索节点 v;
如果节点 v 的状态是「访问中」,则找到有向图中的环,因此不存在拓扑排序;
如果节点 v 的状态是「已访问」,则节点 v 已经搜索完成并入栈,节点 u 尚未入栈,因此节点 u 的拓扑顺序一定在节点 v的前面,不需要执行任何操作。
当节点 u 的所有相邻节点的状态都是「已访问」时,将节点 u 的状态更新为「已访问」,并将节点 u 入栈。
当所有节点都访问结束之后,如果没有找到有向图中的环,则存在拓扑排序,所有节点从栈顶到栈底的顺序即为拓扑排序。
这道题里需要将外星文字典中的每个字母看成一个节点,将字母之间的顺序关系看成有向边。对于外星文字典中的两个相邻单词,同时从左到右遍历,当遇到第一个不相同的字母时,该位置的两个字母之间即存在顺序关系。
以下两种情况不存在合法字母顺序:
- 字母之间的顺序关系存在由至少 22 个字母组成的环,例如words=[“a",“b",“a"];
- 相邻两个单词满足后面的单词是前面的单词的前缀,且后面的单词的长度小于前面的单词的长度,例如 words=[“ab",“a"]。
其余情况下都存在合法字母顺序,可以使用拓扑排序得到字典顺序。
public class Offer_II_114 {
Map<Character, List<Character>> edges = new HashMap<>();//用于记录有向边的关系
int[] states = new int[26];//用于记录各个字母的状态 0代表没访问过 1代表正在访问 2代表已经访问过了
boolean valid = true;
Stack<Character> stack = new Stack<>();
public String alienOrder(String[] words) {
for (String str : words) {
int strLength = str.length();
for (int j = 0; j < strLength; j++) {
edges.putIfAbsent(str.charAt(j), new ArrayList<>());
}
}
//将所有相邻字符串中存在的字母之间的有向边关系获取到
for (int i = 1; i < words.length && valid; i++) {
addEdge(words[i - 1], words[i]);
}
//通过构建的有向图的关系来判断是否有环 并且保存结果到stack中
Set<Character> keySet = edges.keySet();
for (char ch : keySet) {
if (states[ch - 'a'] == 0) DFS(ch);
}
if (!valid) return "";
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) sb.append(stack.pop());
return sb.toString();
}
private void DFS(char ch) {
states[ch - 'a'] = 1;
List<Character> list = edges.get(ch);
for (char u : list) {
if (states[u - 'a'] == 0) {
DFS(u);
if (!valid) return;
}
else if (states[u - 'a'] == 1) {
valid = false;
return;
}
}
states[ch - 'a'] = 2;
stack.push(ch);
}
//通过两个相邻的字符串word与word1的关系来得到字母的有向图关系
private void addEdge(String word, String word1) {
int length1 = word.length();
int length2 = word1.length();
int length = Math.min(length1, length2);
int index = 0;
while (index < length) {
if (word.charAt(index) != word1.charAt(index)) {
edges.get(word.charAt(index)).add(word1.charAt(index));
break;
}
index++;
}
//如果存在[“ab",“a"]这种情况 直接是环
if (index == length && length1 > length2) valid = false;
}
}