Suppose you are at a party with n
people (labeled from 0
to n - 1
) and among them, there may exist one celebrity. The definition of a celebrity is that all the other n - 1
people know him/her but he/she does not know any of them.
Now you want to find out who the celebrity is or verify that there is not one. The only thing you are allowed to do is to ask questions like: "Hi, A. Do you know B?" to get information of whether A knows B. You need to find out the celebrity (or verify there is not one) by asking as few questions as possible (in the asymptotic sense).
You are given a helper function bool knows(a, b)
which tells you whether A knows B. Implement a function int findCelebrity(n)
. There will be exactly one celebrity if he/she is in the party. Return the celebrity's label if there is a celebrity in the party. If there is no celebrity, return -1
.
Example 1:
Input: graph = [
[1,1,0],
[0,1,0],
[1,1,1]
]
Output: 1 Explanation: There are three persons labeled with 0, 1 and 2. graph[i][j] = 1 means person i knows person j, otherwise graph[i][j] = 0 means person i does not know person j. The celebrity is the person labeled as 1 because both 0 and 2 know him but 1 does not know anybody.
Example 2:
Input: graph = [
[1,0,1],
[1,1,0],
[0,1,1]
]
Output: -1 Explanation: There is no celebrity.
Note:
- The directed graph is represented as an adjacency matrix, which is an
n x n
matrix wherea[i][j] = 1
means personi
knows personj
whilea[i][j] = 0
means the contrary. - Remember that you won't have direct access to the adjacency matrix.
這道題讓我們在一群人中尋找名人,所謂名人就是每個人都認識他,他卻不認識任何人,限定了只有1個或0個名人,給定了一個 API 函數,輸入a和b,用來判斷a是否認識b,讓我們盡可能少的調用這個函數,來找出人群中的名人。博主最先想的方法是建立個一維數組用來標記每個人的名人候選狀態,開始均初始化為 true,表示每個人都是名人候選人,然后一個人一個人的驗證其是否為名人,對於候選者i,遍歷所有其他人j,如果i認識j,或者j不認識i,說明i不可能是名人,那么標記其為 false,然后驗證下一個候選者,反之如果i不認識j,或者j認識i,說明j不可能是名人,標記之。對於每個候選者i,如果遍歷了一圈而其候選者狀態仍為 true,說明i就是名人,返回即可,如果遍歷完所有人沒有找到名人,返回 -1,參見代碼如下:
解法一:
bool knows(int a, int b); class Solution { public: int findCelebrity(int n) { vector<bool> candidate(n, true); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (candidate[i] && i != j) { if (knows(i, j) || !knows(j, i)) { candidate[i] = false; break; } else { candidate[j] = false; } } } if (candidate[i]) return i; } return -1; } };
我們其實可以不用一維數組來標記每個人的狀態,對於不是名人的i,直接 break,繼續檢查下一個,但是由於沒有標記后面的候選人的狀態,所以有可能會重復調用一些 knows 函數,所以下面這種方法雖然省了空間,但是調用 knows 函數的次數可能會比上面的方法次數要多,參見代碼如下:
解法二:
bool knows(int a, int b); class Solution { public: int findCelebrity(int n) { for (int i = 0, j = 0; i < n; ++i) { for (j = 0; j < n; ++j) { if (i != j && (knows(i, j) || !knows(j, i))) break; } if (j == n) return i; } return -1; } };
下面這種方法是網上比較流行的一種方法,設定候選人 res 為0,原理是先遍歷一遍,對於遍歷到的人i,若候選人 res 認識i,則將候選人 res 設為i,完成一遍遍歷后,來檢測候選人 res 是否真正是名人,如果判斷不是名人,則返回 -1,如果並沒有沖突,返回 res,參見代碼如下:
解法三:
bool knows(int a, int b); class Solution { public: int findCelebrity(int n) { int res = 0; for (int i = 0; i < n; ++i) { if (knows(res, i)) res = i; } for (int i = 0; i < n; ++i) { if (res != i && (knows(res, i) || !knows(i, res))) return -1; } return res; } };
由熱心網友 fgvlty 提醒,還可以進一步減少 API 的調用量,找候選者的方法跟上面相同,但是在驗證的時候,分為兩段,先驗證候選者前面的所有人,若候選者認識任何人,或者任何人不認識候選者,直接返回 -1。再驗證候選者后面的人,這時候只需要驗證是否有人不認識候選者就可以了,因為在最開始找候選者的時候就已經保證了候選者不會認識后面的任何人,參見代碼如下:
解法四:
bool knows(int a, int b); class Solution { public: int findCelebrity(int n) { int res = 0; for (int i = 0; i < n; ++i) { if (knows(res, i)) res = i; } for (int i = 0; i < res; ++i) { if (knows(res, i) || !knows(i, res)) return -1; } for (int i = res + 1; i < n; ++i) { if (!knows(i, res)) return -1; } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/277
類似題目:
Find the Town Judge
參考資料:
https://leetcode.com/problems/find-the-celebrity/
https://leetcode.com/problems/find-the-celebrity/discuss/71227/Java-Solution.-Two-Pass