六月集训(30)拓扑排序

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

原题链接


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

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

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

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

        示例 1:

        输入:recipes = [“bread”], ingredients = [[“yeast”,“flour”]], supplies = [“yeast”,“flour”,“corn”]

        输出:[“bread”]

        示例 2:

        输入:recipes = [“bread”,“sandwich”], ingredients = [[“yeast”,“flour”],[“bread”,“meat”]], supplies = [“yeast”,“flour”,“meat”]

        输出:[“bread”,“sandwich”]

        提示:

        n == recipes.length == ingredients.length

        1 <= n <= 100

        1 <= ingredients[i].length, supplies.length <= 100

        1 <= recipes[i].length, ingredients[i][j].length, supplies[k].length <= 10

        recipes[i], ingredients[i][j] 和 supplies[k] 只包含小写英文字母。

        所有 recipes 和 supplies 中的值互不相同。

        ingredients[i] 中的字符串互不相同。


        这道题没什么好说的,明确了recipes中某个元素也可能是另一个的原材料的话就十分简单了。吧recipes[i]的ingredients中的所有非supplies的元素反向建边,然后拓扑排序,最后返回拓扑序即可。

class Solution {
    unordered_set<string> s;
    unordered_map<string ,vector<int>> e;
    int cnt[110];
public:
    vector<string> findAllRecipes(vector<string>& recipes, vector<vector<string>>& ingredients, vector<string>& supplies) {
        for(auto i:supplies){
            s.insert(i);
        }
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<recipes.size();++i){
            for(auto tmp:ingredients[i]){
                if(s.find(tmp)==s.end()){
                    ++cnt[i];
                    e[tmp].push_back(i);
                }
            }
        }
        queue<int> q;
        for(int i=0;i<recipes.size();++i){
            if(!cnt[i]){
                q.push(i);
            }
        }
        vector<string> ans;
        while(!q.empty()){
            int u=q.front();
            q.pop();
            ans.push_back(recipes[u]);
            for(auto v:e[recipes[u]]){
                --cnt[v];
                if(cnt[v]==0){
                    q.push(v);
                }
            }
        }
        return ans;
    }
};

2.LeetCode:剑指 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:

        输入:nums = [1,2,3], sequences = [[1,2],[1,3]]

        输出:false

        示例 2:

        输入:nums = [1,2,3], sequences = [[1,2]]

        输出:false

        示例 3:

        输入:nums = [1,2,3], sequences = [[1,2],[1,3],[2,3]]

        输出:true

        提示:

        n == nums.length
1 <= n <= 104

        nums 是 [1, n] 范围内所有整数的排列

        1 <= sequences.length <= 104

        1 <= sequences[i].length <= 104

        1 <= sum(sequences[i].length) <= 105

        1 <= sequences[i][j] <= n

        sequences 的所有数组都是 唯一 的

        sequences[i] 是 nums 的一个子序列


        本题看似说着麻烦又是超序列又是最短的,其实就是把sequences数组建边之后比较nums和拓扑序是否相同。

        至于建边和拓扑排序还是需要一点技巧的。

        (1):由于这里每个sequens元素都是一个子序列,也就是说拓扑排序的时候起点只能有一个,所以排序过程中队列出现了多个元素直接返回false。

        (2)建边的时候对于sequence的每个元素,我们从下标为1的元素开始,让他的前一个元素向他建立一条边,这是使得出现次数较多的边尽可能被晚搜到并且不会重复搜索。

class Solution {
    #define maxn 10010
    int cnt[maxn];
    vector<int> e[maxn];
public:
    bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
        memset(cnt,0,sizeof(cnt));
        vector<int> tmp;
        for(int i=0;i<sequences.size();++i){
            for(int j=1;j<sequences[i].size();++j){
               int u=sequences[i][j-1];
               int v=sequences[i][j];
               e[u].push_back(v);
               ++cnt[v]; 
            }
        }
        queue<int> q;
        for(int i=1;i<=nums.size();++i){
            if(!cnt[i]){
                q.push(i);
                tmp.push_back(i);
            }
        }
        if(q.size()>1){
            return false;
        }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<e[u].size();++i){
                int v=e[u][i];
                --cnt[v];
                if(cnt[v]==0){
                    q.push(v);
                    tmp.push_back(v);
                }
            }
            if(q.size()>1){
                return false;
            }
        }
        if(tmp.size()>nums.size()){
            return false;
        }
        for(int i=0;i<nums.size();++i){
            if(tmp[i]!=nums[i]){
                return false;
            }
        }
        return true;
    }
};

3.LeetCode:1462. 课程表 IV

原题链接


        你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1 。
        你会得到一个数组 prerequisite ,其中 prerequisites[i] = [ai, bi] 表示如果你想选 bi 课程,你 必须 先选 ai 课程。

        有的课会有直接的先修课程,比如如果想上课程 1 ,你必须先上课程 0 ,那么会以 [0,1] 数对的形式给出先修课程数对。

        先决条件也可以是 间接 的。如果课程 a 是课程 b 的先决条件,课程 b 是课程 c 的先决条件,那么课程 a 就是课程 c 的先决条件。

        你也得到一个数组 queries ,其中 queries[j] = [uj, vj]。对于第 j 个查询,您应该回答课程 uj 是否是课程 vj 的先决条件。

        返回一个布尔数组 answer ,其中 answer[j] 是第 j 个查询的答案。

        示例 1

        输入:numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]

        输出:[false,true]

        示例 2:

        输入:numCourses = 2, prerequisites = [], queries = [[1,0],[0,1]]

        输出:[false,false]

        示例 3:

        输入:numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]

        输出:[true,true]

        提示:

        2 <= numCourses <= 100

        0 <= prerequisites.length <= (numCourses * (numCourses - 1) / 2)

        prerequisites[i].length == 2

        0 <= ai, bi <= n - 1

        ai != bi

        每一对 [ai, bi] 都 不同

        先修课程图中没有环。

        0 <= ui, vi <= n - 1

        ui != vi


        这一系列题目老拓扑排序了,本题也依然是拓扑排序。

        这里就根据prerequisite数组反向建边,然后去深搜构造每个课程的先决条件,也就是下列代码中的to数组(我的先决条件的先决条件也是我的先决条件)然后根据询问进行查找即可。

class Solution {
    #define maxn 110
    vector<int> e[maxn];
    vector<int> to[maxn];
    int vis[maxn];
    void dfs(int u,vector<int>& to){
        if(!vis[u]){
            vis[u]=1;
            to.push_back(u);
            for(int i=0;i<e[u].size();++i){
                dfs(e[u][i],to);
            }
        }
    }
public:
    vector<bool> checkIfPrerequisite(int numCourses, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {
        int i,j;
        vector<bool> ans;
        for(int i=0;i<prerequisites.size();++i){
            int u=prerequisites[i][0];
            int v=prerequisites[i][1];
            e[v].push_back(u);
        }
        for(int i=0;i<numCourses;++i){
            memset(vis,0,sizeof(vis));
            dfs(i,to[i]);
        }
        for(int i=0;i<queries.size();++i){
            int u=queries[i][0];
            int v=queries[i][1];
            bool flag=false;
            for(int j=0;j<to[v].size();++j){
                if(to[v][j]==u){
                    flag=true;
                }
            }
            ans.push_back(flag);
        }
        return ans;
    }
};

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


        这道题就是玩文字游戏的题目了,如果我们发现这是一道拓扑排序的题目还比较好做,但是如果从字符串的方式来就显得无从下手了。

        首先我们抓住题目的两个题眼,题目中明确说出了word是一个字典,并且其中的每个元素都按照字典序构造好了。也就是说,word数组就是把左右的单词按照字典序排序的数组。


        那么如果同一个单词不相邻的重复出现(这种代表有环),或者某个单词是其之前的元素前缀且长度比他小,这种是字典序矛盾,返回空字符串。比如[x,z,x],[wrxx,wrt,wrx]。这里第二种比较好辨别,我们让每个元素与数组其他元素进行比较,如果其中一个遍历完毕后前缀相等,就比较二者长度,如果前者比后者长度大就返回true。(为什么不吧等于也返回并特判j!=i+1是因为[abc,ab]这种情况下我们进行特判的话就会对本该是非法的情况判断为正确),重复出现的情况我们在建边的时候排除。

        怎么建边呢?利用我们排除第二种情况时的判断,当发现两个字符串对应位置不同的时候就让前一个字符串的该位元素向后一个字符串的该位元素建边,并统计入度接着返回。这样重复出现的情况就被排除了。删除了重复的情况很好理解,为什么要让对应位置建边可能会有疑问,这其实也好理解,如果二者存在同样的前缀,那么自然是前者对应位置的字典序比后者小,但是再之后的就不确定了所以此时建边之后直接返回,而如果是没有公共前缀就更显然是前者第一个字母的字典序比后者小了。

        接着走一遍拓扑排序得到拓扑序即可。最后如果某个点还有入度,代表有环,我们返回空串。

class Solution {
    vector<int> e[26];
    int cnt[26],vis[26];
    bool check(string &a,string &b){
        int len=min(a.size(),b.size());
        for(int i=0;i<len;++i){
            if(a[i]!=b[i]){
                e[a[i]-'a'].push_back(b[i]-'a');
                ++cnt[b[i]-'a'];
                return false;
            }
        }
        return a.size()>b.size();
    }
    string topsort()
    {
        int i;
        queue<int> q;
        string ans;
        for(i=0;i<26;++i){
            if(vis[i]&&!cnt[i]){
                q.push(i);
                ans+='a'+i;
            }
        }
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(auto v:e[u]){
                --cnt[v];
                if(cnt[v]==0){
                    q.push(v);
                    ans+='a'+v;
                }
            }
        }
        for(int i=0;i<26;++i){
            if(vis[i]&&cnt[i]){
                return "";
            }
        }
        return ans;
    }
public:
    string alienOrder(vector<string>& words) {
        int n=words.size();
        memset(vis,0,sizeof(vis));
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;++i){
            for(int j=i+1;j<n;++j){
                if(check(words[i],words[j])){
                    return "";
                }
            }
        }
        for(int i=0;i<n;++i){
            for(int j=0;j<words[i].size();++j){
                vis[words[i][j]-'a']=1;
            }
        }
        return topsort();
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值