剑指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;
    }
};

剑指 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;
    }
};

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

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;
    }
};

剑指 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;        
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值