拓扑排序:不是排序,是基于有向图的一种遍历算法
大致流程:
1、对每一条边u->v,对b的入度加一
2、遍历所有入度为0的点,放入队列;
3、取出队列中的点s,将它遍历到的边s->t中的t的入度减一;如果入度减到0,则将t放入队列;
4、遍历完毕,剩下所有没有遍历到的点,就一定在某个环上。
今日题目累计时长:2h
题目描述:
你有 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;
}
};
题目描述:
给定一个长度为 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;
}
};
题目描述:
现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。
给定一个字符串列表 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();
}
};