leetcode
leetcode 251 ~ 300
火星词典

火星词典

难度:

标签:

题目描述

代码结果

运行时间: 26 ms, 内存: 16.1 MB


/*
题目思路:
1. 构建字符的入度表和邻接表。
2. 使用流式API对单词列表进行处理, 构建图并更新入度。
3. 使用流式API进行拓扑排序, 如果排序结果字符数与所有唯一字符数一致, 返回排序结果, 否则返回""。
*/
 
import java.util.*;
import java.util.stream.*;
 
public class AlienDictionaryStream {
    public String alienOrder(String[] words) {
        Map<Character, Long> inDegree = words.stream()
            .flatMapToInt(String::chars)
            .mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
 
        Map<Character, List<Character>> adjList = words.stream()
            .flatMapToInt(String::chars)
            .mapToObj(c -> (char) c)
            .distinct()
            .collect(Collectors.toMap(c -> c, c -> new ArrayList<>()));
 
        IntStream.range(0, words.length - 1).forEach(i -> {
            String first = words[i];
            String second = words[i + 1];
            if (first.length() > second.length() && first.startsWith(second)) {
                throw new IllegalArgumentException();
            }
            IntStream.range(0, Math.min(first.length(), second.length())).filter(j -> first.charAt(j) != second.charAt(j)).findFirst().ifPresent(j -> {
                adjList.get(first.charAt(j)).add(second.charAt(j));
                inDegree.put(second.charAt(j), inDegree.get(second.charAt(j)) + 1);
            });
        });
 
        Queue<Character> queue = inDegree.entrySet().stream()
            .filter(entry -> entry.getValue() == 0)
            .map(Map.Entry::getKey)
            .collect(Collectors.toCollection(LinkedList::new));
 
        StringBuilder sb = new StringBuilder();
        while (!queue.isEmpty()) {
            char current = queue.poll();
            sb.append(current);
            adjList.get(current).forEach(neighbor -> {
                inDegree.put(neighbor, inDegree.get(neighbor) - 1);
                if (inDegree.get(neighbor) == 0) {
                    queue.add(neighbor);
                }
            });
        }
 
        return sb.length() < inDegree.size() ? "" : sb.toString();
    }
}
 

解释

方法:

该题解利用拓扑排序的方法来确定火星字典中字符的顺序。首先,建立一个字符间的依赖图,即从一个单词到下一个单词比较不同字符的位置,形成有向边。使用一个入度表来记录每个字符的入度。然后,使用一个队列实现广度优先搜索(BFS),将入度为0的字符依次加入队列。每次从队列中取出一个字符,表示这个字符在字典顺序中的下一个位置,然后减少其相邻节点的入度,并检查新的入度为0的节点加入队列。这个过程持续到队列为空。如果最终结果的长度等于字符集大小,表示找到了有效的顺序;如果不是,则可能存在环,表示没有有效的字符顺序。

时间复杂度:

O(n * L + V + E)

空间复杂度:

O(V + E)

代码细节讲解

🦆
在构建依赖图时,如何确保只添加有效的有向边,避免由于单词顺序错误造成的不必要的边?
在构建依赖图时,只有当第一个不同的字符出现时,才添加有向边。这种方法确保了只有在两个字符之间存在明显的顺序关系时,才会形成依赖关系。通过对相邻单词进行逐字符比较,并在发现第一个不同的字符时停止比较,可以避免错误的依赖关系。这样做不仅提高了算法的效率,还避免了因错误单词顺序(例如字典序错误的输入)而造成的不必要的依赖关系。
🦆
代码中提到,如果一个单词是另一个单词的前缀,则可能直接返回空字符串。这种情况下,为什么不能简单地忽略这种前缀关系而继续构建剩余的依赖关系?
如果一个单词是另一个单词的前缀,并且前者出现在后者之后,这在语言的字典排序中通常表示一个冲突或错误,因为较短的单词应该先出现。这种情况下直接返回空字符串是因为这反映了单词列表中潜在的逻辑错误或输入错误,导致无法构建有效的字符顺序。继续构建可能会忽略这种根本的错误,导致生成的字符顺序不符合实际的字典序规则。
🦆
拓扑排序中,如果存在环怎样检测?题解中似乎没有明显的环检测步骤,请问这是如何实现的?
在拓扑排序的实现中,环的检测是通过检查最终排序结果的长度是否等于图中节点的总数来间接实现的。如果存在环,则图中至少有一部分节点的入度永远不会变为0,因此这些节点不会被加入结果序列中,导致最终结果的长度小于节点总数。如果长度等于节点总数,则表明没有环,所有节点都被正确排序。
🦆
当队列中的字符被全部处理后,结果字符串的长度与字符集大小相等才认为排序成功。在哪些情况下,这两者的长度会不相等?
结果字符串的长度与字符集大小不相等通常发生在存在环的情况下。如果图中存在环,则环内的节点因为相互依赖,其入度不可能变为0,这些节点就不会被加入到结果字符串中。此外,如果输入数据或构建图的逻辑有误,也可能导致无法将所有字符正确排序。这些情况下,结果字符串的长度会小于字符集的大小,表明排序失败。

相关问题

课程表 II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

 

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]

示例 3:

输入:numCourses = 1, prerequisites = []
输出:[0]

 

提示:
  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 互不相同