1. 程式人生 > >演算法設計與分析第三次作業

演算法設計與分析第三次作業

#leetcode 685.Redundant Connection II

In this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) for which all other nodes are descendants of this node, plus every node has exactly one parent, except for the root node which has no parents.
The given input is a directed graph that started as a rooted tree with N nodes (with distinct values 1, 2, …, N), with one additional directed edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] that represents a directed edge connecting nodes u and v, where u is a parent of child v.
Return an edge that can be removed so that the resulting graph is a rooted tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array.
Note:
The size of the input 2D-array will be between 3 and 1000.
Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array.

題目給定一個樹再加一條多餘的邊,要求我們找出這個多餘的邊,這時我們可以分兩種情況,一種情況是有一個頂點入度為2,或者是存在環。

之所以只可能有一個頂點入度為2而不是多個頂點或者是一個頂點入度為3其實很好理解,無論怎麼樣去掉一條邊後這兩種情況都還會有一個頂點入度為2或者以上,顯然不可能為樹,而有多個頂點入度為0也不可能,這種情況不可能通過刪除邊來變成一個樹,所以只可能有一個頂點入度為0或者沒有頂點入度為0,這兩種情況下不是樹的唯一可能就是還存在環。

現在先考慮有一個頂點入度為2的情況,首先我們不難把這條指向此頂點的邊找出來,我一開始覺得如果是這種情況的話只要刪除後面出現的那個就可以了,但是事實上如果隨便刪的話剩下的圖還有可能存在環,所以其實還是要對剩下的圖進行判斷。

如果沒有頂點入度為2,那麼就只需要找環就可以了,首先可以確定只有一個環,因為兩個環不可能通過只刪除一條邊就變成沒有環,最後返回環的最後一條邊就可以了,這個過程其實可以和上述對剩下的圖判斷有沒有環一起執行,具體程式碼如下:

class Solution {
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
    	//tree[i]中存節點i的父節點label
        int tree[edges.size() + 1];
        vector<
int> suspect1,suspect2; for(int i = 0 ; i <= edges.size(); i++){ tree[i] = -1; } for(int i = 0; i < edges.size(); i++){ if(tree[edges[i][1]] != -1){ //如果有入度為2的節點,則儲存衝突的邊,把第一條邊保留,第二條邊儲存後移出圖 suspect1.push_back(tree[edges[i][1]]); suspect1.push_back(edges[i][1]); suspect2 = edges[i]; edges[i].clear(); } else tree[edges[i][1]] = edges[i][0]; } /*如果出現了入度為2的節點,現在edges中存的是去掉了suspect2的情況,否則為原圖,parent陣列存當前發現的根節點*/ int parent[edges.size() + 1]; //一開始還沒進行搜尋,預設根節點為自己 for(int i = 1; i <= edges.size(); i++){ parent[i] = i; } for(int i = 1; i <= edges.size(); i++){ //跳過suspect2 if(edges[i].empty()) continue; int u = edges[i][0], v = edges[i][1]; int root_u = findRoot(parent,u); //如果去掉suspect2之後仍有環,並且suspect2存在,那麼說明需要去掉的邊實際為suspect1,否則如果suspect2不存在,則返回對應的邊即可*/ if(root_u == v){ if(suspect1.empty()) return edges[i]; else return suspect1; } parent[v] = root_u; } return suspect2; } //一路向上找最根部的節點,並把沿途的值也賦值為根節點的值,避免重複搜尋 int findRoot(int* parent, int tofind){ if(parent[tofind] != tofind){ parent[tofind] = findRoot(parent, parent[tofind]); } return parent[tofind]; } };

這裡需要注意一些點:

1.題目要求如果有多條邊,那麼返回最後出現的那條,之所以找環過程中一找到就可以確定是最後一條是因為我們已經確定只有一個環了,那麼我們可以確定這條邊在環裡面,那麼前面的那些邊自然就已經找到了,所以在整個數組裡面這條邊自然是環的所有邊裡面最後出現的。

2.之所以要採用一個parent陣列來存目前找到的此節點的根節點,是因為如果每次都要一步步向上去尋找根節點會浪費非常多的時間,比如1->2->3,如果我確定了2是1的孩子,那兒麼如果3是2的孩子,那麼3自然是1的後代,而對於找環來說儲存3是2的孩子這個資訊並沒有直接作用,還不如直接儲存3是1的後代,中間的節點並不重要。

#leetcode133.Clone Graph

Given the head of a graph, return a deep copy (clone) of the graph. Each node in the graph contains a label (int) and a list (List[UndirectedGraphNode]) of its neighbors. There is an edge between the given node and each of the nodes in its neighbors.

題目本身並不難理解,就是要深複製一個圖,我一開始覺得只要先淺複製一個節點,然後對於鄰居節點遞迴呼叫深複製函式就好了,但是這樣交上去後發現run time error,後面仔細想了後發現這不一定是一個無環圖,如果有環的話就會出現遞迴迴圈呼叫的情況,導致RE, 所以其實就針對這一點做一些判斷就好了,我採用了一個map來判斷某個節點是否已經被產生過,如果已經被產生了就直接用之前產生的節點地址就好了,而不需要再去呼叫深複製函式,具體程式碼如下:

/**
 * Definition for undirected graph.
 * struct UndirectedGraphNode {
 *     int label;
 *     vector<UndirectedGraphNode *> neighbors;
 *     UndirectedGraphNode(int x) : label(x) {};
 * };
 */
class Solution {
public:
    UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
    	if(!node){
    		return nullptr;
    	}
    	UndirectedGraphNode* ans;
    	if(labelMap.count(node->label)){
    		ans = labelMap[node->label];
    		return ans;
    	}
        ans = new UndirectedGraphNode(node->label);
        labelMap[node->label] = ans;
        for(int i = 0; i <(int)node->neighbors.size(); i++){
        	UndirectedGraphNode* tmp = cloneGraph(node->neighbors[i]);
        	if(tmp){
        		ans->neighbors.push_back(tmp);
        	}
        }
        return ans;
    }
private:
	map<int,UndirectedGraphNode*> labelMap;
};

不過這種做法的缺點是在呼叫完一次之後會有“後遺症”,需要對其中的labelMap進行清空,一種比較可行的做法是再封裝一個函式f,讓函式f來呼叫上述函式並清空labelMap即可#leetcode 685.Redundant Connection II

In this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) for which all other nodes are descendants of this node, plus every node has exactly one parent, except for the root node which has no parents.
The given input is a directed graph that started as a rooted tree with N nodes (with distinct values 1, 2, …, N), with one additional directed edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] that represents a directed edge connecting nodes u and v, where u is a parent of child v.
Return an edge that can be removed so that the resulting graph is a rooted tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array.
Note:
The size of the input 2D-array will be between 3 and 1000.
Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array.

題目給定一個樹再加一條多餘的邊,要求我們找出這個多餘的邊,這時我們可以分兩種情況,一種情況是有一個頂點入度為2,或者是存在環。

之所以只可能有一個頂點入度為2而不是多個頂點或者是一個頂點入度為3其實很好理解,無論怎麼樣去掉一條邊後這兩種情況都還會有一個頂點入度為2或者以上,顯然不可能為樹,而有多個頂點入度為0也不可能,這種情況不可能通過刪除邊來變成一個樹,所以只可能有一個頂點入度為0或者沒有頂點入度為0,這兩種情況下不是樹的唯一可能就是還存在環。

現在先考慮有一個頂點入度為2的情況,首先我們不難把這條指向此頂點的邊找出來,我一開始覺得如果是這種情況的話只要刪除後面出現的那個就可以了,但是事實上如果隨便刪的話剩下的圖還有可能存在環,所以其實還是要對剩下的圖進行判斷。

如果沒有頂點入度為2,那麼就只需要找環就可以了,首先可以確定只有一個環,因為兩個環不可能通過只刪除一條邊就變成沒有環,最後返回環的最後一條邊就可以了,這個過程其實可以和上述對剩下的圖判斷有沒有環一起執行,具體程式碼如下:

class Solution {
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
    	//tree[i]中存節點i的父節點label
        int tree[edges.size() + 1];
        vector<int> suspect1,suspect2;
        for(int i = 0 ; i <= edges.size(); i++){
        	tree[i] = -1;
        }
        for(int i = 0; i < edges.size(); i++){
        	if(tree[edges[i][1]] != -1){
        		//如果有入度為2的節點,則儲存衝突的邊,把第一條邊保留,第二條邊儲存後移出圖
        		suspect1.push_back(tree[edges[i][1]]);
                suspect1.push_back(edges[i][1]);
        		suspect2 = edges[i];
        		edges[i].clear();
        	}
        	else tree[edges[i][1]] = edges[i][0];
        }
        /*如果出現了入度為2的節點,現在edges中存的是去掉了suspect2的情況,否則為原圖,parent陣列存當前發現的根節點*/
        int parent[edges.size() + 1];
        //一開始還沒進行搜尋,預設根節點為自己
        for(int i = 1; i <= edges.size(); i++){
        	parent[i] = i;
        }
        for(int i = 1; i <= edges.size(); i++){
        	//跳過suspect2
        	if(edges[i].empty()) 
        		continue;
        	int u = edges[i][0], v = edges[i][1];
        	int root_u = findRoot(parent,u);
        	//如果去掉suspect2之後仍有環,並且suspect2存在,那麼說明需要去掉的邊實際為suspect1,否則如果suspect2不存在,則返回對應的邊即可*/
        	if(root_u == v){
        		if(suspect1.empty())
        			return edges[i];
        		else return suspect1;
        	}
        	parent[v] = root_u;
        }
        return suspect2;
    }
    //一路向上找最根部的節點,並把沿途的值也賦值為根節點的值,避免重複搜尋
    int findRoot(int* parent, int tofind){
    	if(parent[tofind] != tofind){
    		parent[tofind] = findRoot(parent, parent[tofind]);
    	}
    	return parent[tofind];
    }
};

這裡需要注意一些點:

1.題目要求如果有多條邊,那麼返回最後出現的那條,之所以找環過程中一找到就可以確定是最後一條是因為我們已經確定只有一個環了,那麼我們可以確定這條邊在環裡面,那麼前面的那些邊自然就已經找到了,所以在整個數組裡面這條邊自然是環的所有邊裡面最後出現的。

2.之所以要採用一個parent陣列來存目前找到的此節點的根節點,是因為如果每次都要一步步向上去尋找根節點會浪費非常多的時間,比如1->2->3,如果我確定了2是1的孩子,那兒麼如果3是2的孩子,那麼3自然是1的後代,而對於找環來說儲存3是2的孩子這個資訊並沒有直接作用,還不如直接儲存3是1的後代,中間的節點並不重要。

#leetcode133.Clone Graph

Given the head of a graph, return a deep copy (clone) of the graph. Each node in the graph contains a label (int) and a list (List[UndirectedGraphNode]) of its neighbors. There is an edge between the given node and each of the nodes in its neighbors.

題目本身並不難理解,就是要深複製一個圖,我一開始覺得只要先淺複製一個節點,然後對於鄰居節點遞迴呼叫深複製函式就好了,但是這樣交上去後發現run time error,後面仔細想了後發現這不一定是一個無環圖,如果有環的話就會出現遞迴迴圈呼叫的情況,導致RE, 所以其實就針對這一點做一些判斷就好了,我採用了一個map來判斷某個節點是否已經被產生過,如果已經被產生了就直接用之前產生的節點地址就好了,而不需要再去呼叫深複製函式,具體程式碼如下:

/**
 * Definition for undirected graph.
 * struct UndirectedGraphNode {
 *     int label;
 *     vector<UndirectedGraphNode *> neighbors;
 *     UndirectedGraphNode(int x) : label(x) {};
 * };
 */
class Solution {
public:
    UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
    	if(!node){
    		return nullptr;
    	}
    	UndirectedGraphNode* ans;
    	if(labelMap.count(node->label)){
    		ans = labelMap[node->label];
    		return ans;
    	}
        ans = new UndirectedGraphNode(node->label);
        labelMap[node->label] = ans;
        for(int i = 0; i <(int)node->neighbors.size(); i++){
        	UndirectedGraphNode* tmp = cloneGraph(node->neighbors[i]);
        	if(tmp){
        		ans->neighbors.push_back(tmp);
        	}
        }
        return ans;
    }
private:
	map<int,UndirectedGraphNode*> labelMap;
};

不過這種做法的缺點是在呼叫完一次之後會有“後遺症”,需要對其中的labelMap進行清空,一種比較可行的做法是再封裝一個函式f,讓函式f來呼叫上述函式並清空labelMap即可