1. 程式人生 > >LeetCode題解:Minimum Height Trees

LeetCode題解: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.

翻譯一下就是,給定n個節點和若干條邊,把它畫成一棵樹,並且返回這樣所有的節點——當把它們看作是這棵樹的根時,這棵樹的深度是最小的。

果然會超時的鄰接表暴力法

#include <iostream> 
#include <string>
#include <vector>
#include <queue>
using namespace std; 
class Solution {
public:
    //按層遍歷獲取樹的高度
int get_height(int root, vector <vector <int>> & map, int n) { int * flag = new int[n](); queue <int> Q; int label = 0; Q.push(root); while(!Q.empty()) { int count = Q.size(); label++; for (; count > 0; count--) { int head = Q.front(); flag[head] = 1; for (int p = 0; p < n; p++) { if (map[head][p] && flag[p] == 0) { Q.push(p); } } Q.pop(); } } delete[] flag; return label; } //返回最小的節點 vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) { vector <vector <int>> map(n, vector<int>(n,0)); for (auto & point: edges) { map[point.first][point.second] = 1; map[point.second][point.first] = 1; } int min_height = n; int pre_height = 0; vector <int> height; for (int i = 0; i < n; i++) { pre_height = get_height(i, map, n); min_height = min_height > pre_height ? pre_height : min_height; height.push_back(pre_height); } vector <int> result; for (int i = 0; i < n; i++) { if (height[i] == min_height) { result.push_back(i); } } return result; } }; int main() { Solution s; vector<pair<int, int>> edges; edges.push_back(pair<int, int>(0,3)); edges.push_back(pair<int, int>(1,3)); edges.push_back(pair<int, int>(2,3)); edges.push_back(pair<int, int>(4,3)); edges.push_back(pair<int, int>(5,4)); vector<int> a = s.findMinHeightTrees(6, edges); for (auto i: a) { cout << i << endl; } return 0; }

在節點數較小的情況下表現尚可,但是在最後幾個例子裡面還是超時了。如果一定要說這個演算法有什麼亮點的話,那大概就是我第一次用vector快捷地實現了一個二維陣列,以及在BFS演算法的基礎上改進了一點點以實現分層遍歷。

這回居然還沒過的連結串列暴力法

有圖為證

說真的,倒在最後一個樣例上是最讓人不爽的事情之一了。
——魯迅

小改了一下演算法,在圖的最大度大於1的情況下,不再試圖令度為1的節點也就是葉子結點成為根節點,順帶把鄰接表換成了類似於連結串列的結構,但還是過不了最後的超大樣例。難道是因為STL太慢了嗎。。。

class Solution {
public:
    //按層遍歷獲取樹的高度
    int get_height(int root, vector <vector <int>> & map, int n) {
        int * flag = new int[n]();
        queue <int> Q;
        int label = 0;
        Q.push(root);
        while(!Q.empty()) {
            int count = Q.size();
            label++;
            for (; count > 0; count--) {
                int head = Q.front();
                flag[head] = 1;
                for (auto & p : map[head]) {
                    if (flag[p] == 0) {
                        Q.push(p);
                        flag[p] = 1;
                    }
                }
                Q.pop();
            }
        }
        delete[] flag;
        return label;
    }
    //返回最小的節點
    vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
    	vector <int> result;
    	//所有節點都是葉子結點有且只有這一種情況 
    	if (n == 2) {
    		result.push_back(0);
    		result.push_back(1);
    		return result;
		}
        vector <vector <int>> map(n);
        for (auto & point: edges) {
        	map[point.first].push_back(point.second);
            map[point.second].push_back(point.first);
        }
        int min_height = n;
        int pre_height = 0;
        vector <int> height;
        for (int i = 0; i < n; i++) {
        	if (map[i].size() == 1) {
        		height.push_back(-1);
        		continue;
			}
        	pre_height = get_height(i, map, n);
        	min_height = min_height > pre_height ? pre_height : min_height;
        	height.push_back(pre_height);
        }
        for (int i = 0; i < n; i++) {
        	if (height[i] == min_height) {
        		result.push_back(i);
        	}
        }
        return result;
    }
};

解法三:從入海口回溯三江源

這是我黔驢技窮之後看到大佬的思路豁然開朗做出來的,能想出這個解法真的令人驚歎。這道題最關鍵的一點在於,最終解只有一個或者是兩個相互指向的節點,因為當有三個長度相同的根時,就必然會形成一個環。因此我們需要做的,僅僅是從葉子節點往上,層層回溯,直到只剩下兩個或一個節點。

要留意的一個重要情況是,可能會有葉子節點直接指向根節點,如果此時就把根節點加入佇列,根節點就會被踢出去,為此,還需要實時標記各個節點的度(這裡的度是與尚未加入佇列的節點建立起來的連線的數目),僅僅把那些度為1的節點加入佇列。

class Solution {
public:
    //返回最小的節點
    vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
    	vector <int> result;
        vector <vector <int>> map(n);
        //標記訪問過的節點的總數 
        int visited = 0;
        //標記是否訪問某個節點
		int * flag = new int[n](); 
        //標記各個節點的度數
		int * degrees = new int[n](); 
        for (auto & point: edges) {
        	map[point.first].push_back(point.second);
            map[point.second].push_back(point.first);
            degrees[point.first]++;
            degrees[point.second]++;
        }
        queue <int> Q;
        for (int i = 0; i < n; i++) {
            if (degrees[i] <= 1) {
                Q.push(i);
                flag[i] = 1;
                visited++;
            }
        }
        //第二種情況是有還沒有訪問到的節點,針對一個根節點兩個葉子結點的情況 
        while(Q.size() > 2 || n > visited) {
            int count = Q.size();
            for (; count > 0; count--) {
                int head = Q.front();
                for (auto & p : map[head]) {
                    if (flag[p] == 1) {
                    	continue;
                    }
                    degrees[p]--;
                    if (degrees[p] == 1) {
                    	Q.push(p);
                    	flag[p] = 1;
                        visited++;
					}
                }
                Q.pop();
            }
        }
        while(!Q.empty()) {
            result.push_back(Q.front());
            Q.pop();
        }
        delete [] flag;
        delete [] degrees;
        return result;
    }
};