Leetcode-Algorithm-Graph-310
-
题目:
-
For a undirected graph with tree characteristics, we can choose any node as the root. The result graph is then a rooted tree. Among all possible rooted trees, those with minimum height are called minimum height trees (MHTs). Given such a graph, write a function to find all the MHTs and return a list of their root labels.
(对于一个具有树的特性的无向图,我们可以选择任意顶点作为根节点,得到的是一个有根树。在所有有根图中,具有最小高度的树称为最小高度树MHT。给定这样一个无向图,写一个函数找到所有的MHT,并返回这些MHT的根节点的列表。)
注意:
- 树的高度是由根节点往下最长的路径决定的。
例子:
给定:n = 4, edges = [[1, 0], [1, 2], [1, 3]]
返回:[1]
给定:n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
返回:[3, 4]
题解:
方法1:(DFS-深度优先搜索)
按照题意,从每个顶点开始做深度优先搜索,记录其最长的路径为以该顶点根节点的树的高度。最后找出高度最小的顶点返回即可。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
unordered_map<int, unordered_set<int>> graph; // 记录每个顶点的连接关系
//根据给出的边向容器内填入数据
for (int i = 0; i < edges.size(); ++i) {
graph[edges[i].first].insert(edges[i].second);
graph[edges[i].second].insert(edges[i].first);
}
//不断地摘除叶子顶点,当剩下1个或两个顶点时,剩下的顶点就是答案。
while (graph.size() > 2) {
unordered_map<int, int> delNode;
for (auto node : graph) {
if (node.second.size() == 1)
delNode[node.first] = *(node.second.begin());
}
for (auto node : delNode) {
graph.erase(graph.find(node.first));
graph[node.second].erase(graph[node.second].find(node.first));
}
}
vector<int> result;
for (auto node : graph)
result.push_back(node.first);
if (graph.empty())
for (int i = 0; i < n; ++i)
result.push_back(i);
return result;
}
};
分析:
由于要对所有n个顶点做DFS,一次DFS的时间复杂度是
O(|V|+|E|)
,由树的特征可以知道
|E|=|V|−1
,且
n=|V|
,所以总的时间复杂度会
O(n2)
。
方法2:(BFS-广度优先搜索)
首先,必须理解,高度最小的树最多只有两棵。假定有三棵,从树的性质可知,三棵树对应的顶点必定是连通的且只有一条路径把它们都串接起来。设三棵树a, b, c的高度为h,若以中间的顶点b为根节点,那么它的左右子树(分别以a和b为根节点)的高度分别为h,那么b的高度应该是h+1,与假设不符。所以最多只有两个顶点,它们对应的MHT高度是最小的。然后,要想高度最小,必须是分支顶点,而不是叶子顶点,而且其左右子树高度不超过1。因此我们可以从叶子顶点出发,一层一层将叶子顶点除去,知道图中只剩下一个或者两个顶点,剩下的顶点就是要返回的结果。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
unordered_map<int, unordered_set<int>> graph; // 记录每个顶点的连接关系
//根据给出的边向容器内填入数据
for (int i = 0; i < edges.size(); ++i) {
graph[edges[i].first].insert(edges[i].second);
graph[edges[i].second].insert(edges[i].first);
}
//遍历所有顶点找到当前的叶子顶点
unordered_map<int, int> leaves;
for (auto node : graph) {
if (node.second.size() == 1)
leaves[node.first] = *(node.second.begin());
}
//不断地摘除叶子顶点,当剩下1个或两个顶点时,剩下的顶点就是答案。
while (graph.size() > 2) {
unordered_map<int, int> nextLeaves;
for (auto node : leaves) {
graph.erase(graph.find(node.first));
graph[node.second].erase(graph[node.second].find(node.first));
if (graph[node.second].size() == 1) {
nextLeaves[node.second] = *(graph[node.second].begin());
}
}
leaves = nextLeaves;
}
vector<int> result;
for (auto node : graph)
result.push_back(node.first);
if (graph.empty())
for (int i = 0; i < n; ++i)
result.push_back(i);
return result;
}
};
分析:
由于要找到叶子顶点,所以需要遍历一次图,时间复杂度大概是
O(n)
,然而找到一次叶子顶点后,在删除叶子顶点是可以判断与叶子顶点相连的顶点是否会成为下一次需要删除的叶子顶点,因此这个过程也这是需要
O(n)
,综上可知,总的时间复杂度为
O(n)
。