• 剑指offer专项突击版第39天


    剑指 Offer II 114. 外星文字典

    拓扑排序

    1. 根据给定的字符串顺序构造拓扑图确定字母的先后关系(外星文比较规则和我们的普通的字符串一样)。
    2. 可能会有些 w o r d word word 的字母不在拓扑图中,说明这些字母无法确定顺序,故可以在任意位置,这里我就直接插到尾部了。
    3. 该代码是优先级低的字母指向高的,那么弹出的时候必然符合字符递增顺序。最后记得把不在拓扑图但在 w o r d s words words 出现过的字母插到尾部。

    注意两种不合法字母顺序

    1. 若两字符串前缀相同,并且第二串长度小于第一串那么肯定不存在合法字母顺序。
    2. 若拓扑排序后,图所有的点没弹出,那么该拓扑图肯定是不合法的(存在环)。
    class Solution {
    public:
        string alienOrder(vector<string>& words) {
            int n = words.size();
            if(n == 1) return words[0];
            bool st[26] = {0}; //标记words中出现的所有字母
            vector<vector<int>> adj(26); //存储拓扑图
            vector<int> in(26); //拓扑图中点的入度
            string res;
    
            for(int i = 0; i < n; i++)
                for(int j = 0; j < words[i].size(); j++)
                    if(!st[words[i][j]-'a']) st[words[i][j]-'a'] = true;
    	
    		unordered_set<int> us; //记录加入拓扑图的点
            for(int i = 1; i < n; i++) {
                int len = min(words[i-1].size(),words[i].size());
                bool flag = false;
                for(int j = 0; j < len; j++) {
                    int u = words[i-1][j]-'a';
                    int v = words[i][j]-'a';
                    if(u != v) {
                        adj[u].emplace_back(v);
                        in[v]++;
                    	if(!us.count(u)) us.insert(u);
                    	if(!us.count(v)) us.insert(v);
                        flag = true;
                        break;
                    }
                }
                //前缀相同并且前面的长度大于当前长度一定不是有效的
                if(!flag && words[i-1].size() > words[i].size()) return ""; 
            }
            queue<int> que;
            for(int i = 0; i < 26; i++) {
                if(in[i] == 0 && st[i]) { //i为拓扑图中的点且入度为0
                    que.emplace(i);
                    st[i] = false;
                } 
            }
            while(que.size()) {
                int u = que.front(); que.pop();
                us.erase(u); st[u] = false;
                res.push_back((char)(u+'a'));
                for(int v: adj[u]) {
                    in[v]--;
                    if(in[v] == 0) que.emplace(v);
                }
            }
    
            //未弹出所有的点,说明该图出现环,不能进行拓扑排序
    		if(us.size()) return "";
            //其他未加入拓扑图的点顺序就随意了,直接插入即可
            for(int i = 0; i < 26; i++) 
            	if(st[i]) res.push_back((char)(i+'a'));
    		
            return res;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    剑指 Offer II 115. 重建序列

    方法一、拓扑排序

    1. 首先通过 s e q u e n c e s sequences sequences 获得数字先后顺序然后建立拓扑图,接着进行拓扑排序获得拓扑序列。
    2. 容易发现拓扑序列一定是最短的超序列,故不用多加考虑是否为长度最短序列,又由于 s e q u e n c e s sequences sequences n u m s nums nums 的子序列,所以当 n u m s nums nums 是唯一的拓扑序列就说明是唯一最短超序列 ,所以最终我们只需判断拓扑图产生的拓扑序列是否获得唯一即可
    3. 若拓扑排序过程中同时存在多个入度为0的点,那么一定可以产生有多个拓扑序列,这就违反了 n u m s nums nums 是唯一的超序列(拓扑序列)。
    class Solution {
    public:
        bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
            int n = nums.size();
            vector<int> in(n+1);
            vector<unordered_set<int>> adj(n+1);
            for(int i = 0; i < sequences.size(); i++) {
                for(int j = 1; j < sequences[i].size(); j++) {
                    int &cur = sequences[i][j], &pre = sequences[i][j-1]; 
                    if(!adj[pre].count(cur)) {
                        adj[pre].insert(cur);
                        in[cur]++;
                    }
                }  
            }
            queue<int> que;
            for(int i = 1; i <= n; i++) {
                if(in[i] == 0) que.emplace(i);
            }
            if(que.size() > 1) return false;
            while(que.size()) {
                int u = que.front(); que.pop();
                for(int v: adj[u]) {
                    in[v]--;
                    if(in[v] == 0) {
                        que.emplace(v);
                        if(que.size() > 1) return false;
                    }
                }
            }
            return true;
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    方法二、直接检索(贪心)

    Tips:我对偏序的理解:比如 a>b 属于一个偏序关系,也算是一个偏序对,>就属于偏序,可以替换成其他二元关系函数,拓扑图就是一个非严格偏序集(可能产生多个拓扑序列),题目中求的最短超序列就是一个严格偏序集。

    1. 将问题转化成:当 n u m s nums nums 每个相邻的点的严格偏序关系都存在于 s e q u e n c e s sequences sequences 的偏序集中,那么 n u m s nums nums 一定是最短的超序列。
    2. 这个偏序关系可以通过哈希函数映射。
    3. 唯一性证明:最好可以自己草稿证明,比如样例nums = [1,2,3], sequences = [[1,2],[1,3]],从方法一的拓扑序列思路中想到一种不合法的情况:拓扑图存在多个入度同时为0的点。这种情况等同于 n u m s nums nums 的某个相邻的偏序对不存在 s e q u e n c e s sequences sequences 的偏序集中,并且方法一获得的最短超序列(唯一拓扑序列)的偏序集一定等同于 n u m s nums nums 的偏序集。
    class Solution {
        int make_key(int prev, int next){
            return (prev<<14)|next;
        }
    public:
        bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
            int n = nums.size();
            unordered_set<int> nxt;
            for(auto &s:sequences){
                for(int i=1, l=s.size(); i<l; ++i){
                    nxt.insert(make_key(s[i-1], s[i])); //s[i-1] 在 s[i] 的关系哈希
                }
            }
            for(int i=1; i<n; ++i){
                if(nxt.find(make_key(nums[i-1], nums[i]))==nxt.end()){
                    return false;
                }
            }
            return true;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    剑指 Offer II 116. 省份数量

    并查集

    获得所有连通块的数量即可。

    class Solution {
    public:
        vector<int> f;
        int getf(int v) {
            if(v == f[v]) return v;
            else return f[v] = getf(f[v]);
        }
    
        bool merge(int a,int b) {
            int ta = getf(a);
            int tb = getf(b);
            if(ta != tb) {
                f[tb] = ta;
                return false;
            } else return true;
        }
    
        int findCircleNum(vector<vector<int>>& isConnected) {
            int n = isConnected.size();
            f.resize(n);
            for(int i = 0; i < f.size(); i++) f[i] = i;
    
            for(int i = 0; i < n; i++)
                for(int j = 0; j < n; j++)
                    if(isConnected[i][j] == 1) {
                        merge(i,j);
                    }
            int res = 0;
            for(int i = 0; i < n; i++) if(f[i] == i) res++;        
            return res;        
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    【超硬核】从0-1构建UI组件库
    史上最全MongoDB之安全认证
    【网络安全】黑客自学笔记
    2022主流技术 Appium+IOS 自动化测试环境搭建
    Sulfo-CY3 azide水溶性荧光探针 星戈瑞
    莞中集训8.1
    Spring Boot项目介绍(值得学习,超详细)
    C++空间配置器
    DOM系列之创建元素
    Android 逆向入门保姆级教程
  • 原文地址:https://blog.csdn.net/hys__handsome/article/details/126482636