[LeetCode]剑指 Offer II 114. 外星文字典

75 篇文章 0 订阅

题目

剑指 Offer II 114. 外星文字典

剑指 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] 仅由小写英文字母组成

解法

同269题

注意题意中这两点的描述

字符串 s 字典顺序小于 字符串 t 有两种情况:

  • 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
  • 如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。

实际举例:

以下两种情况不存在合法字母顺序:

  • 字母之间的顺序关系存在由至少 22 个字母组成的环,例如words=[“a",“b",“a"];

  • 相邻两个单词满足后面的单词是前面的单词的前缀,且后面的单词的长度小于前面的单词的长度,例如 words=[“ab",“a"]。

方法1:拓扑排序+BFS

        Map<Character, List<Character>> edges = new HashMap<>();//字符间边的关系
        Map<Character, Integer> indegrees = new HashMap<>();//统计某个字符的入度
        boolean valid = true;//判断是否需要提前退出

        public String alienOrder(String[] words) {
            //建图,完成拓扑排序的准备工作
            int n = words.length;
            for (String word : words) {
                for (char c : word.toCharArray()) {
                    edges.putIfAbsent(c, new ArrayList<>());//给每个字符添加一个相邻边
                }
            }
            for (int i = 1; i < n && valid; i++) {
                addEdge(words[i - 1], words[i]);
            }
            if (!valid) return "";
            //bfs
            Queue<Character> q = new LinkedList<>();
            for (char u : edges.keySet()) {//将入度为0的字符加入到队列中
                if (!indegrees.containsKey(u)) {
                    q.offer(u);
                }
            }
            StringBuilder sb = new StringBuilder();//记录弹出的顺序
            while (!q.isEmpty()) {
                char u = q.poll();
                sb.append(u);
                for (char v : edges.get(u)) {//遍历u的邻居
                    indegrees.put(v, indegrees.get(v) - 1);
                    if (indegrees.get(v) == 0) {//入度为0后,该节点转入队列中
                        q.offer(v);
                    }
                }
            }
            //["z","x","a","zb","zx"]
            //对于前四个字符串 排序是zxab 但是来了zx后 x又得排序到b之后,但是b之前已经出现了x,是矛盾的
            //这个case范围的是"" 如果不加上判断,返回的是"b", "b"的邻居"x"在入度减一后并没有立马减少为0,bfs提前结束
            return sb.length() == edges.size() ? sb.toString() : "";
        }


        public void addEdge(String prev, String cur) {
            int m = prev.length(), n = cur.length();
            int len = Math.min(m, n);
            int i = 0;
            for (; i < len; i++) {
                char u = prev.charAt(i), v = cur.charAt(i);
                if (u != v) {
                    if (u == 'b') {
                        System.out.println();
                    }
                    edges.get(u).add(v);
                    indegrees.put(v, indegrees.getOrDefault(v, 0) + 1);
                    break;
                }
            }
            //如果"abc" "ab"的这种case,提前返回""
            if (i == len && m > n) {
                valid = false;
            }
        }

方法2:拓扑排序+DFS

官方dfs拓扑排序的思路:

由于拓扑排序的顺序和搜索完成的顺序相反,因此需要使用一个栈存储所有已经搜索完成的节点。深度优先搜索的过程中需要维护每个节点的状态,每个节点的状态可能有三种情况:「未访问」、「访问中」和「已访问」。初始时,所有节点的状态都是「未访问」。

每一轮搜索时,任意选取一个「未访问」的节点 u,从节点 u 开始深度优先搜索。将节点 u的状态更新为「访问中」,对于每个与节点 u 相邻的节点 v,判断节点 v 的状态,执行如下操作:

  • 如果节点 v的状态是「未访问」,则继续搜索节点 v;

  • 如果节点 v 的状态是「访问中」,则找到有向图中的环,因此不存在拓扑排序;

  • 如果节点 v 的状态是「已访问」,则节点 v 已经搜索完成并入栈,节点 u 尚未入栈,因此节点 u 的拓扑顺序一定在节点 v 的前面,不需要执行任何操作。

     static final int VISITING = 1, VISITED = 2;
        Map<Character, List<Character>> edges = new HashMap<>();//字符间边的关系
        Map<Character, Integer> states = new HashMap<>();//统计某个字符的状态
        boolean valid = true;
        char[] paths;
        int index;

        public String alienOrder(String[] words) {
            //建图,完成拓扑排序的准备工作
            int n = words.length;
            for (String word : words) {
                for (char c : word.toCharArray()) {
                    edges.putIfAbsent(c, new ArrayList<>());//给每个字符添加一个相邻边
                }
            }
            for (int i = 1; i < n && valid; i++) {
                addEdge(words[i - 1], words[i]);
            }
            if (!valid) return "";
            //dfs
            paths = new char[edges.size()];
            index = edges.size() - 1;
            for (char u : edges.keySet()) {
                if (!states.containsKey(u)) {
                    dfs(u);
                }
            }
            if (!valid) return "";
            return String.valueOf(paths);
        }

        private void dfs(char u) {
            states.put(u, VISITING);//当前节点标记为「访问中」
            for (char v : edges.get(u)) {
                if (!states.containsKey(v)) {//节点v没有被访问
                    dfs(v);//继续遍历v
                    if (!valid) return;//如果发现有不符合条件的 ,提前结束
                } else if (states.get(v) == VISITING) //第二次进入v,说明有环
                {
                    valid = false;
                    return;
                }
            }
            states.put(u, VISITED);//u这个节点是安全的,标记为「已访问」
            paths[index--] = u;//记录u在栈的位置
        }


        public void addEdge(String prev, String cur) {
            int m = prev.length(), n = cur.length();
            int len = Math.min(m, n);
            int i = 0;
            for (; i < len; i++) {
                char u = prev.charAt(i), v = cur.charAt(i);
                if (u != v) {
                    edges.get(u).add(v);
                    break;
                }
            }
            if (i == len && m > n) {
                valid = false;
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值