Java&C++题解与拓展——leetcode剑指Offer II 114.外星文字典【拓扑排序】

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路一:建图+拓扑排序

  • 链式前向星建图,通过遍历比较两个单词字符来构建,类似双指针;
  • 建图完成从字典序最小的点(入度为 0 0 0)开始进行BFS,依次入队出队,出队节点数量与总数相等则为合法拓扑图,否则有问题。

Java

class Solution {
    int N = 26, M = N * N, idx = 0, cnt = 0;
    int[] head = new int[N], edge = new int[M], next = new int[M];
    int[] in = new int[N], out = new int[N]; // 入度出度
    boolean[] vis = new boolean[N];
    // 建有向图
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
        out[a]++;
        in[b]++;
    }
    // 遍历单词a和b构建字符拓扑图
    boolean build(String a, String b) {
        int n = a.length(), m = b.length(), len = Math.min(n, m);
        for(int i = 0; i < len; i++) {
            int c1 = a.charAt(i) - 'a', c2 = b.charAt(i) - 'a';
            if(c1 != c2) {
                add(c1, c2);
                return true;
            }
        }
        return n <= m; // 短者应在前
    }
    public String alienOrder(String[] words) {
        int n = words.length;
        Arrays.fill(head, -1);
        for(int i = 0; i < n; i++) {
            for(char c : words[i].toCharArray())
                if(!vis[c - 'a'] && ++cnt >= 0)
                    vis[c - 'a'] = true;
            for(int j = 0; j < i; j++) {
                if(!build(words[j], words[i]))
                    return "";
            }
        }
        // BFS拓扑图输出结果
        Deque<Integer> que = new ArrayDeque<>();
        for(int i = 0; i < 26; i++)
            if(vis[i] && in[i] == 0)
                que.addLast(i);
        StringBuilder res = new StringBuilder();
        while(!que.isEmpty()) {
            int cur = que.pollFirst();
            res.append((char)(cur + 'a'));
            for(int i = head[cur]; i != -1; i = next[i]) {
                int j = edge[i];
                if(--in[j] == 0)
                    que.addLast(j);
            }
        }
        return res.length() == cnt ? res.toString() : "";
    }
}
  • 时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n n n是数组 w o r d s words words的长度;建图复杂度为 O ( n 3 ) O(n^3) O(n3),BFS构建答案复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

C++【超时】

嘤嘤嘤链式前向星的C++好像就从来没搞通过……

const int N = 26, M = N * N;
class Solution {
    int idx = 0, cnt = 0;
    int head[N], edge[M], next[M];
    int in[N], out[N]; // 入度出度
    bool vis[N];
public:
    // 建有向图
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
        out[a]++;
        in[b]++;
    }
    // 遍历单词a和b构建字符拓扑图
    bool build(string a, string b) {
        int n = a.size(), m = b.size(), len = min(n, m);
        for(int i = 0; i < len; i++) {
            int c1 = a[i] - 'a', c2 = b[i] - 'a';
            if(c1 != c2) {
                add(c1, c2);
                return true;
            }
        }
        return n <= m; // 短者应在前
    }

    string alienOrder(vector<string>& words) {
        int n = words.size();
        memset(head, -1, N);
        for(int i = 0; i < n; i++) {
            for(auto c : words[i])
                if(!vis[c - 'a'] && ++cnt >= 0)
                    vis[c - 'a'] = true;
            for(int j = 0; j < i; j++) {
                if(!build(words[j], words[i]))
                    return "";
            }
        }
        // BFS拓扑图输出结果
        queue<int> que;
        for(int i = 0; i < 26; i++)
            if(vis[i] && in[i] == 0)
                que.push(i);
        string res;
        while(!que.empty()) {
            int cur = que.front();
            que.pop();
            res += cur + 'a';
            for(int i = head[cur]; i != -1; i = next[i]) {
                int j = edge[i];
                if(--in[j] == 0)
                    que.push(j);
            }
        }
        return res.size() == cnt ? res : "";
    }
};
  • 时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n n n是数组 w o r d s words words的长度;建图复杂度为 O ( n 3 ) O(n^3) O(n3),BFS构建答案复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

思路二:拓扑排序+BFS

  • 和上面差不多,但仅记录入度。

Java

class Solution {
    Map<Character, List<Character>> edges = new HashMap<Character, List<Character>>();
    Map<Character, Integer> in = new HashMap<Character, Integer>();
    boolean valid = true;
    public void build(String bef, String aft) {
        int lb = bef.length(), la = aft.length();
        int length = Math.min(lb, la);
        int idx = 0;
        while(idx < length) {
            char cb = bef.charAt(idx), ca = aft.charAt(idx);
            if(cb != ca) {
                edges.get(cb).add(ca);
                in.put(ca, in.getOrDefault(ca, 0) + 1);
                break;
            }
            idx++;
        }
        if(idx == length && lb > la)
            valid = false;
    }

    public String alienOrder(String[] words) {
        int len = words.length;
        for(String w : words) {
            int wlen = w.length();
            for(int j = 0; j < wlen; j++) {
                char c = w.charAt(j);
                edges.putIfAbsent(c, new ArrayList<Character>());
            }
        }
        for(int i = 1; i < len && valid; i++)
            build(words[i - 1], words[i]);
        if(!valid)
            return "";
        Deque<Character> que = new ArrayDeque<Character>();
        Set<Character> letter = edges.keySet();
        for(char l : letter)
            if(!in.containsKey(l))
                que.offer(l);
        StringBuffer res = new StringBuffer();
        while(!que.isEmpty()) {
            char u = que.pollFirst();
            res.append(u);
            List<Character> adj = edges.get(u);
            for(char v : adj) {
                in.put(v, in.get(v) - 1);
                if(in.get(v) == 0)
                    que.addLast(v);
            }
        }
        return res.length() == edges.size() ? res.toString() : "";
    }
}
  • 时间复杂度: O ( n × L + ∣ Σ ∣ ) O(n\times L+|Σ|) O(n×L+Σ),其中 n n n是数组 w o r d s words words的长度, L L L是字典中单词的平均长度, Σ Σ Σ是字典中的字母集合;构造图复杂度为 O ( n × L ) O(n\times L) O(n×L),DFS复杂度为 O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ)
  • 空间复杂度: O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ),用于存有向图,最多包含 n − 1 n-1 n1条边和 ∣ Σ ∣ |Σ| Σ个节点。

C++

class Solution {
public:
    unordered_map<char, vector<char>> edges;
    unordered_map<char, int> in;
    bool valid = true;
    void build(string bef, string aft) {
        int lb = bef.size(), la = aft.size();
        int length = min(lb, la);
        int idx = 0;
        while(idx < length) {
            char cb = bef[idx], ca = aft[idx];
            if(cb != ca) {
                edges[cb].emplace_back(ca);
                in[ca] += 1;
                break;
            }
            idx++;
        }
        if(idx == length && lb > la)
            valid = false;
    }

    string alienOrder(vector<string>& words) {
        int len = words.size();
        for(auto w : words) {
            int wlen = w.size();
            for(int j = 0; j < wlen; j++) {
                char c = w[j];
                if(!edges.count(c))
                    edges[c] = vector<char>();
            }
        }
        for(int i = 1; i < len && valid; i++)
            build(words[i - 1], words[i]);
        if(!valid)
            return "";
        queue<int> que;
        for(auto [e,_] : edges)
            if(!in.count(e))
                que.emplace(e);
        string res;
        while(!que.empty()) {
            char u = que.front();
            que.pop();
            res.push_back(u);
            for(char v : edges[u]) {
                in[v]--;
                if(in[v] == 0)
                    que.emplace(v);
            }
        }
        return res.size() == edges.size() ? res : "";
    }
};
  • 时间复杂度: O ( n × L + ∣ Σ ∣ ) O(n\times L+|Σ|) O(n×L+Σ),其中 n n n是数组 w o r d s words words的长度, L L L是字典中单词的平均长度, Σ Σ Σ是字典中的字母集合;构造图复杂度为 O ( n × L ) O(n\times L) O(n×L),DFS复杂度为 O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ)
  • 空间复杂度: O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ),用于存有向图,最多包含 n − 1 n-1 n1条边和 ∣ Σ ∣ |Σ| Σ个节点。

思路三:拓扑排序+DFS

  • 建图和上面差不多,但维护节点状态,包括未访问、访问中、已访问;已访问意味着该节点的所有相邻节点都已被访问。

Java

class Solution {
    static final int VISITING = 1, VISITED = 2;
    Map<Character, List<Character>> edges = new HashMap<Character, List<Character>>();
    Map<Character, Integer> states = new HashMap<Character, Integer>();
    boolean valid = true;
    char[] res;
    int idx;
    public void build(String bef, String aft) {
        int lb = bef.length(), la = aft.length();
        int length = Math.min(lb, la);
        int idx = 0;
        while(idx < length) {
            char cb = bef.charAt(idx), ca = aft.charAt(idx);
            if(cb != ca) {
                edges.get(cb).add(ca);
                break;
            }
            idx++;
        }
        if(idx == length && lb > la)
            valid = false;
    }
    public void dfs(char u) {
        states.put(u, VISITING);
        List<Character> adj = edges.get(u);
        for(char v : adj) {
            if(!states.containsKey(v)) {
                dfs(v);
                if(!valid)
                    return;
            }
            else if(states.get(v) == VISITING) {
                valid = false;
                return;
            }
        }
        states.put(u, VISITED);
        res[idx] = u;
        idx--;
    }

    public String alienOrder(String[] words) {
        int len = words.length;
        for(String w : words) {
            int wlen = w.length();
            for(int j = 0; j < wlen; j++) {
                char c = w.charAt(j);
                edges.putIfAbsent(c, new ArrayList<Character>());
            }
        }
        for(int i = 1; i < len && valid; i++)
            build(words[i - 1], words[i]);
        res = new char[edges.size()];
        idx = edges.size() - 1;
        Set<Character> letter = edges.keySet();
        for(char l : letter)
            if(!states.containsKey(l))
                dfs(l);
        if(!valid)
            return "";
        return new String(res);
    }
}
  • 时间复杂度: O ( n × L + ∣ Σ ∣ ) O(n\times L+|Σ|) O(n×L+Σ),其中 n n n是数组 w o r d s words words的长度, L L L是字典中单词的平均长度, Σ Σ Σ是字典中的字母集合;构造图复杂度为 O ( n × L ) O(n\times L) O(n×L),DFS复杂度为 O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ)
  • 空间复杂度: O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ),用于存有向图,最多包含 n − 1 n-1 n1条边和 ∣ Σ ∣ |Σ| Σ个节点。

C++

class Solution {
public:
    const int VISITING = 1, VISITED = 2;
    unordered_map<char, vector<char>> edges;
    unordered_map<char, int> states;
    bool valid = true;
    string res;
    int idx;
    void build(string bef, string aft) {
        int lb = bef.size(), la = aft.size();
        int length = min(lb, la);
        int idx = 0;
        while(idx < length) {
            char cb = bef[idx], ca = aft[idx];
            if(cb != ca) {
                edges[cb].emplace_back(ca);
                break;
            }
            idx++;
        }
        if(idx == length && lb > la)
            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;
        res[idx] = u;
        idx--;
    }

    string alienOrder(vector<string>& words) {
        int len = words.size();
        for(auto w : words) {
            int wlen = w.size();
            for(int j = 0; j < wlen; j++) {
                char c = w[j];
                if(!edges.count(c))
                    edges[c] = vector<char>();
            }
        }
        for(int i = 1; i < len && valid; i++)
            build(words[i - 1], words[i]);
        res = string(edges.size(), ' ');
        idx = edges.size() - 1;
        for(auto [e,_] : edges)
            if(!states.count(e))
                dfs(e);      
        if(!valid)
            return "";
        return res;
    }
};
  • 时间复杂度: O ( n × L + ∣ Σ ∣ ) O(n\times L+|Σ|) O(n×L+Σ),其中 n n n是数组 w o r d s words words的长度, L L L是字典中单词的平均长度, Σ Σ Σ是字典中的字母集合;构造图复杂度为 O ( n × L ) O(n\times L) O(n×L),DFS复杂度为 O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ)
  • 空间复杂度: O ( n + ∣ Σ ∣ ) O(n+|Σ|) O(n+Σ),用于存有向图,最多包含 n − 1 n-1 n1条边和 ∣ Σ ∣ |Σ| Σ个节点。

总结

有点复杂的拓扑排序,觉得也可以单独开一篇学习,今天就复习链式前向星,后两个复杂度低点的官方方法也就先划水过了。


欢迎指正与讨论!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值