1. 程式人生 > >leetcode--310. Minimum Height Trees題解

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;
	}
};
  • 遇到的問題

以上演算法的時間複雜度為O(VE)O(V*E)(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;
	}
};