【六月算法】day30 拓扑排序

拓扑排序:不是排序,是基于有向图的一种遍历算法
大致流程:
1、对每一条边u->v,对b的入度加一
2、遍历所有入度为0的点,放入队列;
3、取出队列中的点s,将它遍历到的边s->t中的t的入度减一;如果入度减到0,则将t放入队列;
4、遍历完毕,剩下所有没有遍历到的点,就一定在某个环上。

今日题目累计时长:2h

2115. 从给定原材料中找到所有可以做出的菜

题目描述:

你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。第 i 道菜的名字为 recipes[i] ,如果你有它 所有 的原材料 ingredients[i] ,那么你可以 做出 这道菜。一道菜的原材料可能是 另一道 菜,也就是说 ingredients[i] 可能包含 recipes 中另一个字符串。

同时给你一个字符串数组 supplies ,它包含你初始时拥有的所有原材料,每一种原材料你都有无限多。

请你返回你可以做出的所有菜。你可以以 任意顺序 返回它们。

注意两道菜在它们的原材料中可能互相包含。

思路:

1、每次可以考虑一个不在列表里面的菜,遍历他的原材料,如果原材料都能被组合出来,则他也必然也能被组合出来
2、一次遍历完毕,如果没有新的菜生成,则结束遍历,否则重新遍历
3也可以反向建遍,然后直接做一次拓扑排序即可;

class Solution {
    unordered_map<string, bool> has;
public:
    vector<string> findAllRecipes(vector<string>& rec, vector<vector<string>>& ing, vector<string>& sup) {
        int i, j;
        vector<string> ans;
        for (i = 0; i < sup.size(); ++i) {
            has[sup[i]] = true;
        }
        bool flag = true;
        while (flag) {
            flag = false;
            for (i = 0; i < rec.size(); ++i) {
                if (has.find(rec[i]) != has.end()) {
                    continue;
                }
                for (j = ing[i].size() -1; j >= 0; --j) {
                    if (has.find(ing[i][j]) == has.end()) {
                        break;
                    }
                }
                if (j == -1) {
                    has[rec[i]] = true;
                    ans.push_back(rec[i]);
                    flag = true;
                }
            }
        }
        return ans;
        
    }
};

剑指 Offer II 115. 重建序列

题目描述:

给定一个长度为 n 的整数数组 nums ,其中 nums 是范围为 [1,n] 的整数的排列。还提供了一个 2D 整数数组 sequences ,其中 sequences[i] 是 nums 的子序列。
检查 nums 是否是唯一的最短 超序列 。最短 超序列 是 长度最短 的序列,并且所有序列 sequences[i] 都是它的子序列。对于给定的数组 sequences ,可能存在多个有效的 超序列 。

例如,对于 sequences = [[1,2],[1,3]] ,有两个最短的 超序列 ,[1,2,3] 和 [1,3,2] 。
而对于 sequences = [[1,2],[1,3],[1,2,3]] ,唯一可能的最短 超序列 是 [1,2,3] 。[1,2,3,4] 是可能的超序列,但不是最短的。
如果 nums 是序列的唯一最短 超序列 ,则返回 true ,否则返回 false 。
子序列 是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。

思路:

1、首先,对每组关系建立偏序关系,也就是建立图上的有向边;
2、然后,找到入度为0的点,如果入度为0的点的个数大于1个,说明必然不是唯一的;
3、接着,对剩下的点做一次拓扑排序,每次拓扑的过程中,如果发现队列中的点的个数大于1,则必然不可能产生唯一的序列;
4、最后,拓扑排序完毕,如果还存在入度不为0 的点,说明存在环,也不能产生唯一序列,如果以上条件都满足,则比较唯一序列和拓扑序列是否相等。

class Solution {
    #define maxn  10010
    int deg[maxn];
    vector<int> edges[maxn];

    void addedge(int u, int v) {
        deg[v]++;
        edges[u].push_back(v);
    } 
public:
    bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
        int i, j;
        memset(deg, 0, sizeof(deg));
        vector<int> ans;
        
        //建边
        for (i = 0; i < sequences.size(); ++i) {
            for (j = 1; j < sequences[i].size(); ++j) {
                addedge(sequences[i][j-1], sequences[i][j]);
            }
        }
        queue<int> q;
        //统计入度为0的点。理论上只有一个
        for (i = 1; i <= nums.size(); ++ i) {
            if (!deg[i]) {
                q.push(i);
                ans.push_back(i);
            }
        }
        if (q.size() > 1) {
            return false; //如果入度为0的点的个数大于1个,说明必然不是唯一的;
        }
        //对剩下的点做一次拓扑排序,每次拓扑的过程中,如果发现队列中的点的个数大于1,则必然不可能产生唯一的序列;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (i = 0; i < edges[u].size(); ++ i) {
                int v = edges[u][i];
                if (--deg[v] == 0) {
                    q.push(v);
                    ans.push_back(v);
                }
            }
            if (q.size() > 1) {
                return false; //如果发现队列中的点(入度为0的点)的个数大于1,则必然不可能产生唯一的序列
            }
        }
        if (nums.size() < ans.size()) { //出现环
            return false;
        }
        for (i = 0; i < nums.size(); ++i) {
            if (ans[i] != nums[i]) {
                return false;
            }
        }
        return true;

    }
};

剑指 Offer II 114. 外星文字典

题目描述:

现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。

给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。

请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。

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

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

思路:

1、对于任意两个保持偏序关系的字符串a和b,如果前缀相等,那么下一个字符就是觉得这两个字符真正字典序的时刻;
2、偏序关系建立完毕,利用这个偏序进行拓扑排序;
3、如果执行过程完毕,出现了入度为非0的点,则说明出现了环,直接返回空串。否则就是返回拓扑序列;

class Solution {
    int has[26], deg[26];
    vector<int> edges[26];
    void addedge(int u, int v) {
        ++deg[v];
        edges[u].push_back(v);
    }
    bool check(const string &a, const string &b) {
        int minlen = min(a.size(), b.size());
        for (int i = 0; i < minlen; ++i) {
            if (a[i] != b[i]) {
                addedge(a[i]-'a', b[i]-'a');
                return true;
            }
        }
        return !(a.size() > b.size()); // 在前缀相同的情况下,a的长度大于b的长度,不合法
    }
    string toposort() {
        //找到出现过入度为0的点,是第一个点
        string ans = "";
        queue<int> q;
        for (int i = 0;i < 26; ++i) {
            if (!deg[i] && has[i]) {
                ans += i + 'a';
                q.push(i);
            }
        }

        while (!q.empty()) {
            int u = q.front();
            q.pop();

            for (int i = 0; i < edges[u].size(); ++i) {
                int v = edges[u][i];
                if (--deg[v] == 0) {
                    q.push(v);
                    ans += v + 'a';
                }
            }
        }
        for (int i = 0; i < 26; ++i) {
                if (has[i] && deg[i]) { //出现了环,不合法
                    return "";
                }
            }
        return ans;
    }

public:
    string alienOrder(vector<string>& words) {
        int i, j;
        int n = words.size();
        memset(has, 0, sizeof(has));
        memset(deg, 0, sizeof(deg));
        for (i = 0; i < n; ++i) {
            for (j = i +1 ; j < n; ++j) {
                if (!check(words[i], words[j])) {
                    return "";
                }
            }
            
        }
        //统计出现过的字符,注意转换为数字
        for (i = 0; i < n; ++i) {
            for (j = 0; j < words[i].size(); ++j) {
                has[words[i][j]-'a'] = 1;
            }
        }
        return toposort();

    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值