1. 程式人生 > >547. Friend Circles(python+cpp)

547. Friend Circles(python+cpp)

題目:

here are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.
Given a N*N

matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. And you have to output the total number of friend circles among all the students.
Example 1:

Input:   
      [[1,1,0],  
       [1,1,0],  
       [0,0,1]] 
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.  
The 2nd student himself is in a friend circle. Soreturn 2. 

Example 2:

Input:  
     [[1,1,0],  
      [1,1,1],  
      [0,1,1]] 
Output: 1
Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are 
direct friends,  so the 0th and 2nd students are indirect friends. All of them are in the
same friend circle, so return 1. 

Note:


N is in range [1,200].
M[i][i] = 1 for all students.
If M[i][j] = 1, then M[j][i] = 1.

解釋:
這道題可以用dfs做,也可以用並查集做,dfs的速度居然比並查集快是怎麼回事?
dfs解法,遍歷每一個結點,遍歷到這個結點的時候,會把它所有的直接朋友和間接朋友都遍歷到,而且會將其加入到visited中,所以如果遍歷到的一個結點不在visited中,則證明它不在之前的朋友圈中,可以開啟一個新的朋友圈了,在對其進行dfs。
python程式碼:


class Solution(object):
    def findCircleNum(self, M):
        """
        :type M: List[List[int]]
        :rtype: int
        """
        _set=set()
        
        def dfs(node):
            for i,val in enumerate(M[node]):
                if val and i not in _set:
                    _set.add(i)
                    dfs(i)
        result=0
        for i in range(len(M)):
            if i not in _set:
                result+=1
                dfs(i)
        return result

c++程式碼:

#include <set>
using namespace std;
class Solution {
public:
    set<int>visited;
    int findCircleNum(vector<vector<int>>& M) {
        int result=0;
        for(int i=0;i<M.size();i++)
        {
            if (find(visited.begin(),visited.end(),i)==visited.end())
            {
                result++;
                dfs(M,i);
            }
        }
        return result;
    }
    void dfs(vector<vector<int>> &M,int node)
    {
        for(int i=0;i<M[node].size();i++)
        {
            if (M[node][i] && find(visited.begin(),visited.end(),i)==visited.end())
            {
                visited.insert(i);
                dfs(M,i);
            }   
        } 
    }
};

並查集:《計算機考研機試指南》裡面有一個用並查集求最大的朋友圈的大小的題目,可以參考。首先先把題目所給的矩陣轉換成兩兩連線的形式,也就是如果兩個人是朋友,則用點對(a,b)存起來並存入l陣列edges中,因為一般的並查集就是這種輸入。優化:其實不存edges也可以,在遍歷的過程中完成並查集也可以。
優化前:

class Solution:
    def findCircleNum(self, M):
        """
        :type M: List[List[int]]
        :rtype: int
        """
        if not M:
            return 0
        if M[0].count(1)==len(M):
            return 1
        edges=[]
        for i in range(len(M)):
            for j in range(i,len(M[0])):
                if M[i][j]==1:
                    edges.append([i,j])
        Tree=[-1]*len(M)
        def findRoot(x):
            if Tree[x]==-1:
                return x
            else:
                temp=findRoot(Tree[x])
                Tree[x]=temp
                return temp
        for edge in edges:
            a,b=edge[0],edge[1]
            a=findRoot(a)
            b=findRoot(b)
            #若兩個頂點不在一個集合中,則合併
            if a!=b:
                Tree[a]=b
        return Tree.count(-1)

優化後:

class Solution:
    def findCircleNum(self, M):
        """
        :type M: List[List[int]]
        :rtype: int
        """
        lenM=len(M)
        if M[0].count(1)==lenM:
            return 1
        Tree=[-1]*lenM
        def findRoot(x):
            if Tree[x]==-1:
                return x
            else:
                Tree[x]=findRoot(Tree[x])
                return Tree[x]
        for i in range(lenM):
            for j in range(i,lenM):
                if M[i][j]==1:
                    a,b=i,j
                    a=findRoot(a)
                    b=findRoot(b)
                    #若兩個頂點不在一個集合中,則合併
                    if a!=b:
                        Tree[a]=b         
        return Tree.count(-1)

c++程式碼:

class Solution {
public:
    vector<int>Tree;
    int findCircleNum(vector<vector<int>>& M) {
        int lenM=M.size();
        Tree=vector<int>(lenM,-1);
        for (int i=0;i<lenM;i++)
        {
            for(int j=i;j<lenM;j++)
            {
                if(M[i][j]==1)
                {
                    int a=findRoot(i);
                    int b=findRoot(j);
                    if(a!=b)
                    {
                        Tree[a]=b;
                    }
                }
            }
        }
        return count(Tree.begin(),Tree.end(),-1);
    }
    int findRoot(int x)
    {
        if (Tree[x]==-1)
            return x;
        else
        {
            Tree[x]=findRoot(Tree[x]);
            return Tree[x];
        }
    }
};

總結:
再用並查集解法的時候,不一定非要先存成edges,可以在遍歷的時候直接並查集,這樣更節省時間。