拓扑排序
- 根据给定的字符串顺序构造拓扑图确定字母的先后关系(外星文比较规则和我们的普通的字符串一样)。
- 可能会有些 w o r d word word 的字母不在拓扑图中,说明这些字母无法确定顺序,故可以在任意位置,这里我就直接插到尾部了。
- 该代码是优先级低的字母指向高的,那么弹出的时候必然符合字符递增顺序。最后记得把不在拓扑图但在 w o r d s words words 出现过的字母插到尾部。
注意两种不合法字母顺序
- 若两字符串前缀相同,并且第二串长度小于第一串那么肯定不存在合法字母顺序。
- 若拓扑排序后,图所有的点没弹出,那么该拓扑图肯定是不合法的(存在环)。
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;
}
};
方法一、拓扑排序
- 首先通过 s e q u e n c e s sequences sequences 获得数字先后顺序然后建立拓扑图,接着进行拓扑排序获得拓扑序列。
- 容易发现拓扑序列一定是最短的超序列,故不用多加考虑是否为长度最短序列,又由于 s e q u e n c e s sequences sequences 是 n u m s nums nums 的子序列,所以当 n u m s nums nums 是唯一的拓扑序列就说明是唯一最短超序列 ,所以最终我们只需判断拓扑图产生的拓扑序列是否获得唯一即可。
- 若拓扑排序过程中同时存在多个入度为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
属于一个偏序关系,也算是一个偏序对,>
就属于偏序,可以替换成其他二元关系函数,拓扑图就是一个非严格偏序集(可能产生多个拓扑序列),题目中求的最短超序列就是一个严格偏序集。
- 将问题转化成:当 n u m s nums nums 每个相邻的点的严格偏序关系都存在于 s e q u e n c e s sequences sequences 的偏序集中,那么 n u m s nums nums 一定是最短的超序列。
- 这个偏序关系可以通过哈希函数映射。
- 唯一性证明:最好可以自己草稿证明,比如样例
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;
}
};
并查集
获得所有连通块的数量即可。
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;
}
};