332、给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 出发。
说明:
如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
所有的机场都用三个大写字母表示(机场代码)。
假定所有机票至少存在一种合理的行程。示例 1:
输入: [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
输出: ["JFK", "MUC", "LHR", "SFO", "SJC"]示例 2:
输入: [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出: ["JFK","ATL","JFK","SFO","ATL","SFO"]
解释: 另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。
dfs,带string的有向图,对每条边进行遍历,cnt记录已经遍历了几条边,当遍历所有边时结束dfs。
两个map分别映射str->int,int->str。图用set存边信息,为了使得边按照字典序排。
超时,80 / 80 个通过测试用例,不知道为啥没有返回给我数据...
const int MAX = 1e6;
typedef pair<string,int> E; // {点id,边id}
class Solution {
public:
set<E> gra[MAX]; // 行程的图
map<string, int> mp; // 名称和id的映射,{"JFK", int}
map<int, string> mp2;
vector<bool> mark; //对边进行标记,是否i访问
vector<string> ans;
int edge_num;
bool dfs(int s, int cnt){
if(cnt == edge_num) return true;
for(auto &i:gra[s]){
if(!mark[i.second]){
mark[i.second] = true;
ans.push_back(i.first);
if(dfs(mp[i.first], cnt + 1)) return true;
ans.pop_back();
mark[i.second] = false;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
int n = 0; // 总地点数
edge_num = tickets.size();
mp.clear();
mp2.clear();
for(int i = 0; i < edge_num; i++){
if(!mp.count(tickets[i][0])) {mp[tickets[i][0]] = n; mp2[n++] = tickets[i][0];}
if(!mp.count(tickets[i][1])) {mp[tickets[i][1]] = n; mp2[n++] = tickets[i][1];}
gra[mp[tickets[i][0]]].insert(E(tickets[i][1], i));
}
mark.resize(edge_num, false);
ans.clear();
ans.push_back("JFK");
dfs(mp["JFK"], 0);
return ans;
}
};
修改,把存图的数组改为unordered_map,就不超时啦
const int MAX = 1e6;
typedef pair<string,int> E; // {点id,边id}
class Solution {
public:
unordered_map<string, set<E>> gra;
vector<bool> mark; //对边进行标记,是否i访问
vector<string> ans;
int edge_num;
bool dfs(string s, int cnt){
if(cnt == edge_num) return true;
for(auto &i:gra[s]){
if(!mark[i.second]){
mark[i.second] = true;
ans.push_back(i.first);
if(dfs(i.first, cnt + 1)) return true;
ans.pop_back();
mark[i.second] = false;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
int n = 0; // 总地点数
edge_num = tickets.size();
mark.resize(edge_num, false);
for(int i = 0; i < edge_num; i++) gra[tickets[i][0]].insert(E(tickets[i][1], i));
ans.clear();
ans.push_back("JFK");
dfs("JFK", 0);
return ans;
}
};
结果:
执行用时:56 ms, 在所有 C++ 提交中击败了59.34% 的用户
内存消耗:14.8 MB, 在所有 C++ 提交中击败了100.00% 的用户
补充知识:欧拉路径问题
- 欧拉回路:图G的一个回路,如果恰通过图G的每一条边,则该回路称为欧拉回路,具有欧拉回路的图称为欧拉图。欧拉图就是从图上的一点出发,经过所有边且只能经过一次,最终回到起点的路径。
- 欧拉通路:即可以不回到起点,但是必须经过每一条边,且只能一次。也叫"一笔画"问题。
- 基图:基图是针对有向图的说法,是忽略有向图的方向得到的无向图。
- 欧拉图:存在欧拉回路的图。
定理一:无向图G为欧拉图,当且仅当G为连通图且所有顶点的度为偶数。
定理二:有向图G为欧拉图,当且仅当G的基图连通,且所有顶点的入度等于出度。
339、给出方程式 A / B = k, 其中 A 和 B 均为用字符串表示的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。
示例 :
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]输入为: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size(),即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector<double>类型。
基于上述例子,输入如下:
equations(方程式) = [ ["a", "b"], ["b", "c"] ],
values(方程式结果) = [2.0, 3.0],
queries(问题方程式) = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。
unordered_map记录含string的图关系,dfs,有环图可以dfs时记录父亲节点pre。
// a / b ==》 a->b的有向边权重为2,b->a的有向边权重为1/2.
class Solution {
public:
unordered_map<string, unordered_map<string, double>> gra;
double res;
bool dfs(string a, string pre, string ends, double r){ // 注意因为图中有环,这里加了pre点,记录父亲节点,避免循环dfs。
for(auto &i:gra[a]){
if(i.first == ends) {res = r * i.second; return true;}
if(i.first != pre && dfs(i.first, a, ends, r * i.second)) return true;
}
return false;
}
vector<double> calcEquation(vector<vector<string>>& equations,
vector<double>& values,
vector<vector<string>>& queries) {
for(int i = 0; i < equations.size(); i++){
gra[equations[i][0]].insert(make_pair(equations[i][1], values[i]));
gra[equations[i][1]].insert(make_pair(equations[i][0], 1 / values[i]));
}
vector<double> ans;
for(auto &vec:queries){
double result = -1.0;
if( gra.count(vec[0]) && gra.count(vec[1])){
if(vec[0] == vec[1]) result = 1.0;
else if(dfs(vec[0], "", vec[1], 1.0)) result = res;
}
ans.push_back(result);
}
return ans;
}
};
结果:
执行用时:4 ms, 在所有 C++ 提交中击败了46.65% 的用户
内存消耗:7.5 MB, 在所有 C++ 提交中击败了100.00% 的用户
684、在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
示例 1:
输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
1
/ \
2 - 3示例 2:
输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
| |
4 - 3注意:
输入的二维数组大小在 3 到 1000。
二维数组中的整数在1到N之间,其中N是输入数组的大小。
注意 leetcode报错:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==45==ERROR: AddressSanitizer: stack-overflow on address 0x7fffd4a8dff8 (pc 0x0000003912ba bp 0x7fffd4a8e020 sp 0x7fffd4a8e000 T0)
==45==ABORTING
意味着:死循环
并查集,遍历edges,每加入一边,使得两个不同并查集的点合并,若属于同集合,则必定有环,该边可以删除。
// 并查集
// 遍历edges,每加入一边,使得两个不同并查集的点合并,若属于同集合,则必定有环,该边可以删除。
const int MAX = 1e3+10;
class Solution {
public:
vector<int> tree;
void init(int n){ // 并查集初始化,自成一树
for(int i = 0; i <= n; i++)
tree.push_back(i);
}
int findroot(int x){
if(tree[x] == x) return x;
else{
int t = findroot(tree[x]);
tree[x] = t;
return t;
}
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
init(n);
int id, x, y;
for(int i = 0; i < n; i++){
x = edges[i][0];
y = edges[i][1];
x = findroot(x);
y = findroot(y);
if(x != y) tree[x] = y;
else id = i;
}
return edges[id];
}
};
结果:
执行用时:12 ms, 在所有 C++ 提交中击败了71.37% 的用户
内存消耗:8.3 MB, 在所有 C++ 提交中击败了100.00% 的用户