Leetcode——310 Minimum Height Trees && 332 Reconstruct Itinerary

    本周记录两道图论的题目,第一道是关于BFS的题目,310, Minimum Height Trees ;第二道是关于DFS的题目,332,Reconstruct Itinerary。

一、Minimum Height Trees 

1. 题目描述

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:

Given n = 4edges = [[1, 0], [1, 2], [1, 3]]

        0
        |
        1
       / \
      2   3

return [1]

Example 2:

Given n = 6edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]

     0  1  2
      \ | /
        3
        |
        4
        |
        5

return [3, 4]

题目的大概意思,是给出n个点分别标记为0-n-1, 同时给出节点的无向边集合构成图。在一个图中,任何节点都可以作为根,然后将其他节点连接成树,找出这些树种最小高度的树MHT,返回其根节点的标号。


2. 思路

    一开始采用的方法:先判断n的个数,如果是1个或者2个,那么直接输出即可。如果多于2个,那么,对那些有超过两个邻居的节点进行DFS,求出高度,然后进行比较。结果是超时的。

   根据题目中给的提示,一个图最多有几棵MHT呢?答案是两棵。一棵最小高度的树有两种情况:

  • 一种是两边子树一样高,此时只有一棵MHT;
  • 另一种是左边比右边深一层或者右边比左边深一层,此时有两棵MHT。

     考虑左边比右边深两层的时候,此时必然存在可以取根节点的左子节点作为根,使得右边叶子节点往下拉一层,左边上去一层,得到两棵子树一样高的结果。因此,一个图得到最小高度树就以上所述两种情况。

   采用BFS的思路,只有一个邻居的节点为叶子节点,将这一层叶子节点删除,就会有新的叶子节点出现。依次删除,当最后只剩下一个节点时,即MHT的根;当最后只剩两个节点时,要判断,如果这两个节点彼此相连,说明都可能是MHT的根。


3. 代码

vector<int> findMinHeightTrees(int n, vector<pair<int, int> >& edges) {
	vector<vector<int> > vec(n);
    vector<int> res;
    //先判断n为1和n为2两种简单情况
    if (n == 1)
    	res.push_back(0);
    else if(n == 2){
       res.push_back(0);
       res.push_back(1);
    }
    else{
    	//构建邻接表
    	for(int i = 0; i < edges.size(); i ++)
    	{
    		vec[edges[i].first].push_back(edges[i].second);
    		vec[edges[i].second].push_back(edges[i].first);
    	}
    	queue<int> q;
    	queue<int> p;
    	//将所有叶子节点入队
 	for(int i = 0; i < vec.size(); i ++)
 		if(vec[i].size() == 1){
 			q.push(i);
 		}
 	//删除每一轮的叶子节点,当只剩下一个叶子节点时,说明它是唯一的MHT根;若剩下两个,则要判断,如果这两个点是相连的,则两个点都可以是根
 	while(1){
	  if(q.size() == 1)
		  break;
	  if(q.size() == 2 && vec[q.front()].size() == 1)
		  if(vec[q.front()][0] == q.back())
			  break;
	  while(!q.empty())
	  {
		  int f = q.front();
		  q.pop();
		  int temp = vec[f][0];
		  for(int j = 0; j < vec[temp].size(); j ++)
			  if(vec[temp][j] == f)
  				vec[temp].erase(vec[temp].begin()+j);

		  if(vec[temp].size() == 1){
			  p.push(temp);
		  }
	  }
	  if(!q.empty())
		  break;
	  q = p;
 	}
 	while(!q.empty()){
	  cout << q.front() << endl;
	  res.push_back(q.front());
	  q.pop();
 	}
  }
  return res;
}

二、Reconstruct Itinerary

1. 题目描述

Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK. Thus, the itinerary must begin with JFK.

Note:

  1. If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary ["JFK", "LGA"] has a smaller lexical order than ["JFK", "LGB"].
  2. All airports are represented by three capital letters (IATA code).
  3. You may assume all tickets form at least one valid itinerary.

Example 1:
tickets = [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
Return ["JFK", "MUC", "LHR", "SFO", "SJC"].

Example 2:
tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Return ["JFK","ATL","JFK","SFO","ATL","SFO"].
Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"]. But it is larger in lexical order.

   题目的大概意思,是给出一组字符串对,表示每一张机票出发点和到达点,对这些机票(字符串对)进行重排,使其收尾相连的有序排序,所有机票都要用上。第一个出发点必须是“JFK”。当有两个选择时,选字典序小的。例子如上所示。

2. 思路

   将每一个字符串表示的地址看做一个节点,输入给出的有序对使得节点构成一个图。以“JFK”作为图的根进行遍历。当一个节点有两条路可以选择时,优先考虑字典序小的;然而,有时候选择字典序小的路,不一定能遍历完整个图,所以,需要在保证有路的情况下,选择字典序小的。对输入的有序对构建邻接链表,一般我会采用vector<vector<string> >来实现。然而,使用vector实现存在的问题,是无法使得节点有序的排列。为了使邻接点的选择有序,考虑使用set的方式,一个起点对应多个终点,这些终点按照字典序排好,在遍历时,优先取出来判断是字典序小的。

   遍历采用DFS的方式,从起点JFK开始,对每一个邻接节点,访问后,在邻接表中删除该节点,不断往下遍历,直到没有邻接节点时,将节点加到结果集。返回上一层。

3.代码

map<string, multiset<string> > go; //邻接链表,set可以使得内部值有序
void dfs(vector<string> &res, string s)
{
	while(go[s].size()){
		string e = *go[s].begin();
		go[s].erase(go[s].begin());
		dfs(res,e);
	}
	res.push_back(s);
}
vector<string> findItinerary(vector<pair<string, string> > tickets) {
        vector<string> res;
        for(int i = 0; i < tickets.size(); i ++)
        {
            go[tickets[i].first].insert(tickets[i].second);
        }
       string s = "JFK";
       dfs(res, s);
       reverse(res.begin(), res.end());
       return res;
  }

三、总结

1.DFS使用递归,BFS使用队列;

2.DFS可用于找到最长的连通路径,BFS可以用于处理每一层的节点。

3.邻接链表可以用vector实现,也可以用map实现。


点赞