leetcode--310. Minimum Height Trees題解
題目
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.
Format The graph contains n nodes which are labeled from 0 to n - 1. You will be given the number n and a list of undirected edges (each edge is a pair of labels).
You can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1] is the same as [1, 0] and thus will not appear together in edges. Example 1 :
Input: n = 4, edges = [[1, 0], [1, 2], [1, 3]]
0
|
1
/ \
2 3
Output: [1]
Example 2 :
Input: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2
\ | /
3
|
4
|
5
Output: [3, 4]
方法一:
-
思路與解法
題目要求找到所有最小高度的樹根節點,直觀上做法可以是:將[0~n-1]所有節點都模擬作為根節點來計算其構成的樹的高度,然後尋找最小高度對應的根節點即可。則根據樹的結構,可以得到以下樹的高度計算公式:
height(root) = max(height(children1), height(children2), ……, height(childrenM)) + 1
-
程式碼實現
class Solution {
public:
// dfs遞迴函式:返回以node為根節點的子樹的高度
int dfs(int node, int n, vector<pair<int, int>>& edges, bool* searched){ // searched陣列標記節點是否訪問過
int maxx = 1, temp;
for (auto edge : edges) { // 遍歷所有邊
// 查詢與node相連的另外一個節點
int anotherNode = node;
if (edge.first == node) anotherNode = edge.second;
else if (edge.second == node) anotherNode = edge.first;
// 若該節點尚未被訪問
if (!searched[anotherNode]) {
searched[anotherNode] = true;
// 對應於前面分析的樹的高度計算公式
temp = dfs(anotherNode, n, edges, searched)+1;
if(maxx < temp) maxx = temp;
}
}
return maxx;
}
// findMinHeightTrees函式:返回最小高度樹的根節點的向量
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges){
int heights[n]={0}, minHeight=INT_MAX;
vector<int>minHeightNodes;
// 遍歷所有節點,將所有節點都作為根節點求樹的高度
for(int i=0;i<n;i++){
bool searched[n]={0};
searched[i]=true;
heights[i] = dfs(i, n, edges, searched);
if(minHeight > heights[i]){
minHeight = heights[i];
}
}
// 找出最小高度樹對應的根節點
for(int i=0;i<n;i++){
if(minHeight == heights[i])
minHeightNodes.push_back(i);
}
return minHeightNodes;
}
};
-
遇到的問題
以上演算法的時間複雜度為(V表示節點數量,E表示邊數量),時間複雜度過高,導致submit
不通過。
-
改進與優化
利用map資料結構,來儲存每個節點對應連線的其他節點所組成向量的一個對映map<int, vector<int>>
,(類似於鄰接表)。相對於上面程式碼做了一些剪枝和優化。
class Solution {
public:
int dfs(int node, int n, unordered_map<int, vector<int>>& graph, bool* searched){
int maxx = 1, temp;
for (auto anotherNode : graph[node]) {
if (!searched[anotherNode]) {
searched[anotherNode] = true;
temp = dfs(anotherNode, n, graph, searched)+1;
if(maxx < temp) maxx = temp;
}
}
return maxx;
}
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges){
int heights[n]={0}, minHeight=INT_MAX;
vector<int>minHeightNodes;
unordered_map<int, vector<int>> graph;
for(auto edge : edges) {
graph[edge.first].push_back(edge.second);
graph[edge.second].push_back(edge.first);
}
for(int i=0;i<n;i++){
bool searched[n]={0};
searched[i]=true;
heights[i] = dfs(i, n, graph, searched);
if(minHeight > heights[i]){
minHeight = heights[i];
}
}
for(int i=0;i<n;i++){
if(minHeight == heights[i])
minHeightNodes.push_back(i);
}
return minHeightNodes;
}
};
此時,儘管效率有所提升: 當資料量過大時,提交仍然無法通過:
方法二:
-
思路與解法
根據題意可以知道,返回的節點的數量必定為1或者2。否則的話,假如節點數量為3,因為輸入資料保證該圖具有樹的特徵,所以這三個點必定相連,則這三個點中必定存在一個節點的高度比其他兩個節點的高度要大,所以返回的節點數量不可能大於2。 此時,可以採用剝洋蔥的思想: 第一步,將所有度為0或1的節點加入到佇列中(洋蔥最外層的皮); 第二步,彈出隊首的節點,並將與隊首節點相連的節點的度減少1,此時,將度為0或者1的節點加入到佇列中(不斷將洋蔥外圍的皮剝掉); 第三部,直到所有節點都被加入到佇列一次,且當前佇列只剩下2個或者1個節點時,返回該佇列中的節點即可(返回洋蔥最中心的部分)。
-
程式碼實現
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges){
queue<int>nodes;
int degree[n] = {0};
bool searched[n]={0}; // searched保證了已訪問的節點不會再次被訪問
unordered_map<int, vector<int>> graph;
// 統計所有節點的度、預處理所有邊得到對映
for(auto edge : edges) {
degree[edge.first]++;
degree[edge.second]++;
graph[edge.first].push_back(edge.second);
graph[edge.second].push_back(edge.first);
}
// 將所有度等於0/1的所有節點加入到佇列中
// 問:由於題目保證輸入資料滿足樹的特徵,為什麼會存在度數為0的節點?
// 答:若輸入資料為1 [],此時,該節點度數即為0
for(int i=0;i<n;i++){
if(degree[i] ==1 || degree[i] == 0){
searched[i] = true;
nodes.push(i);
}
}
// 最外層while迴圈判斷佇列是否為空
vector<int>roots;
while(nodes.size()){
int size = nodes.size();
roots.clear(); //每次將roots陣列清空,只有最中心的洋蔥才會保留
// 遍歷每次最外圍的所有節點(相當於洋蔥最外圈)
while(size>0){
int node = nodes.front();
nodes.pop();
size--;
roots.push_back(node);
// 此迴圈,將與外部節點相連的,且度數減1後等於0/1的內部節點加入到佇列中
for(auto anotherNode : graph[node]){
if(!searched[anotherNode]){
if(--degree[anotherNode] == 0 || degree[anotherNode] == 1){
searched[anotherNode] = true;
nodes.push(anotherNode);
}
}
}
}
}
return roots;
}
};