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 ( V E ) 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;
	}
};

猜你喜欢

转载自blog.csdn.net/liuyh73/article/details/82822042