Before attempting this problem, you should be comfortable with:
Logical Deduction - The optimal solution uses elimination to narrow down candidates in a single pass
Graph Concepts - Understanding directed relationships (who knows whom) as a graph problem
Caching/Memoization - Optional optimization to avoid redundant API calls
1. Brute Force
Intuition
A celebrity is someone who is known by everyone but knows nobody. For each person, we can check if they satisfy both conditions: they don't know anyone else, and everyone else knows them. If both conditions hold, that person is the celebrity.
Algorithm
For each person i from 0 to n-1:
Check if i is a celebrity by verifying two conditions for every other person j:
classSolution:deffindCelebrity(self, n:int)->int:
self.n = n
for i inrange(n):if self.is_celebrity(i):return i
return-1defis_celebrity(self, i):for j inrange(self.n):if i == j:continue# Don't ask if they know themselves.if knows(i, j)ornot knows(j, i):returnFalsereturnTrue
publicclassSolutionextendsRelation{privateint numberOfPeople;publicintfindCelebrity(int n){
numberOfPeople = n;for(int i =0; i < n; i++){if(isCelebrity(i)){return i;}}return-1;}privatebooleanisCelebrity(int i){for(int j =0; j < numberOfPeople; j++){if(i == j)continue;// Don't ask if they know themselves.if(knows(i, j)||!knows(j, i)){returnfalse;}}returntrue;}}
functionsolution(knows){functionisCelebrity(i, n){for(let j =0; j < n; j++){if(i === j)continue;if(knows(i, j)||!knows(j, i)){returnfalse;}}returntrue;}returnfunctionfindCelebrity(n){for(let i =0; i < n; i++){if(isCelebrity(i, n)){return i;}}return-1;}}
/* The Knows API is defined in the parent class Relation.
bool Knows(int a, int b); */publicclassSolution:Relation{privateint numberOfPeople;publicintFindCelebrity(int n){
numberOfPeople = n;for(int i =0; i < n; i++){if(IsCelebrity(i)){return i;}}return-1;}privateboolIsCelebrity(int i){for(int j =0; j < numberOfPeople; j++){if(i == j)continue;if(Knows(i, j)||!Knows(j, i)){returnfalse;}}returntrue;}}
/* The knows API is already defined for you.
knows := func(a, b int) bool */funcsolution(knows func(a, b int)bool)func(n int)int{returnfunc(n int)int{
isCelebrity :=func(i int)bool{for j :=0; j < n; j++{if i == j {continue}ifknows(i, j)||!knows(j, i){returnfalse}}returntrue}for i :=0; i < n; i++{ifisCelebrity(i){return i
}}return-1}}
/* The knows API is defined in the parent class Relation.
fun knows(a: Int, b: Int): Boolean */class Solution :Relation(){privatevar numberOfPeople =0overridefunfindCelebrity(n: Int): Int {
numberOfPeople = n
for(i in0 until n){if(isCelebrity(i)){return i
}}return-1}privatefunisCelebrity(i: Int): Boolean {for(j in0 until numberOfPeople){if(i == j)continueif(knows(i, j)||!knows(j, i)){returnfalse}}returntrue}}
/* The knows API is defined in the parent class Relation.
func knows(_ a: Int, _ b: Int) -> Bool */classSolution:Relation{privatevar numberOfPeople =0funcfindCelebrity(_ n:Int)->Int{
numberOfPeople = n
for i in0..<n {ifisCelebrity(i){return i
}}return-1}privatefuncisCelebrity(_ i:Int)->Bool{for j in0..<numberOfPeople {if i == j {continue}ifknows(i, j)||!knows(j, i){returnfalse}}returntrue}}
Time & Space Complexity
We don't know what time and space the knows(...) API uses. Because it's not our concern, we'll assume it's O(1) for the purpose of analysing our algorithm.
Time complexity: O(n2)
Space complexity: O(1)
Where n is the number of nodes in the graph.
2. Logical Deduction
Intuition
Each knows(a, b) call eliminates one person from being a celebrity. If a knows b, then a cannot be the celebrity (celebrities know nobody). If a doesn't know b, then b cannot be the celebrity (everyone must know the celebrity). By iterating through all people once, we can narrow down to a single candidate. We then verify this candidate with a second pass.
Algorithm
Start with candidate = 0.
For each person i from 1 to n-1:
If candidate knows i, update candidate = i (previous candidate is disqualified).
Verify the candidate by checking:
The candidate knows nobody.
Everyone knows the candidate.
Return the candidate if valid, otherwise return -1.
/* The Knows API is defined in the parent class Relation.
bool Knows(int a, int b); */publicclassSolution:Relation{privateint numberOfPeople;publicintFindCelebrity(int n){
numberOfPeople = n;int celebrityCandidate =0;for(int i =0; i < n; i++){if(Knows(celebrityCandidate, i)){
celebrityCandidate = i;}}if(IsCelebrity(celebrityCandidate)){return celebrityCandidate;}return-1;}privateboolIsCelebrity(int i){for(int j =0; j < numberOfPeople; j++){if(i == j)continue;if(Knows(i, j)||!Knows(j, i)){returnfalse;}}returntrue;}}
/* The knows API is already defined for you.
knows := func(a, b int) bool */funcsolution(knows func(a, b int)bool)func(n int)int{returnfunc(n int)int{
isCelebrity :=func(i int)bool{for j :=0; j < n; j++{if i == j {continue}ifknows(i, j)||!knows(j, i){returnfalse}}returntrue}
celebrityCandidate :=0for i :=1; i < n; i++{ifknows(celebrityCandidate, i){
celebrityCandidate = i
}}ifisCelebrity(celebrityCandidate){return celebrityCandidate
}return-1}}
/* The knows API is defined in the parent class Relation.
fun knows(a: Int, b: Int): Boolean */class Solution :Relation(){privatevar numberOfPeople =0overridefunfindCelebrity(n: Int): Int {
numberOfPeople = n
var celebrityCandidate =0for(i in0 until n){if(knows(celebrityCandidate, i)){
celebrityCandidate = i
}}if(isCelebrity(celebrityCandidate)){return celebrityCandidate
}return-1}privatefunisCelebrity(i: Int): Boolean {for(j in0 until numberOfPeople){if(i == j)continueif(knows(i, j)||!knows(j, i)){returnfalse}}returntrue}}
/* The knows API is defined in the parent class Relation.
func knows(_ a: Int, _ b: Int) -> Bool */classSolution:Relation{privatevar numberOfPeople =0funcfindCelebrity(_ n:Int)->Int{
numberOfPeople = n
var celebrityCandidate =0for i in1..<n {ifknows(celebrityCandidate, i){
celebrityCandidate = i
}}ifisCelebrity(celebrityCandidate){return celebrityCandidate
}return-1}privatefuncisCelebrity(_ i:Int)->Bool{for j in0..<numberOfPeople {if i == j {continue}ifknows(i, j)||!knows(j, i){returnfalse}}returntrue}}
Time & Space Complexity
We don't know what time and space the knows(...) API uses. Because it's not our concern, we'll assume it's O(1) for the purpose of analysing our algorithm.
Time complexity: O(n)
Space complexity: O(1)
Where n is the number of nodes in the graph.
3. Logical Deduction with Caching
Intuition
The logical deduction approach may call knows(a, b) multiple times with the same arguments during the verification phase. By caching the results of each call, we can avoid redundant API calls. This is particularly useful when the knows function is expensive to evaluate.
Algorithm
Create a cache to store results of knows(a, b) calls.
Use the same logical deduction approach to find the candidate.
During verification, check the cache before calling knows.
Return the candidate if valid, otherwise return -1.
/* The Knows API is defined in the parent class Relation.
bool Knows(int a, int b); */publicclassSolution:Relation{privateint numberOfPeople;privateDictionary<(int,int),bool> cache =newDictionary<(int,int),bool>();privateboolCachedKnows(int a,int b){if(!cache.ContainsKey((a, b))){
cache[(a, b)]=Knows(a, b);}return cache[(a, b)];}publicintFindCelebrity(int n){
numberOfPeople = n;int celebrityCandidate =0;for(int i =0; i < n; i++){if(CachedKnows(celebrityCandidate, i)){
celebrityCandidate = i;}}if(IsCelebrity(celebrityCandidate)){return celebrityCandidate;}return-1;}privateboolIsCelebrity(int i){for(int j =0; j < numberOfPeople; j++){if(i == j)continue;if(CachedKnows(i, j)||!CachedKnows(j, i)){returnfalse;}}returntrue;}}
/* The knows API is already defined for you.
knows := func(a, b int) bool */funcsolution(knows func(a, b int)bool)func(n int)int{
cache :=make(map[[2]int]bool)
cachedKnows :=func(a, b int)bool{
key :=[2]int{a, b}if val, ok := cache[key]; ok {return val
}
result :=knows(a, b)
cache[key]= result
return result
}returnfunc(n int)int{
isCelebrity :=func(i int)bool{for j :=0; j < n; j++{if i == j {continue}ifcachedKnows(i, j)||!cachedKnows(j, i){returnfalse}}returntrue}
celebrityCandidate :=0for i :=1; i < n; i++{ifcachedKnows(celebrityCandidate, i){
celebrityCandidate = i
}}ifisCelebrity(celebrityCandidate){return celebrityCandidate
}return-1}}
/* The knows API is defined in the parent class Relation.
fun knows(a: Int, b: Int): Boolean */class Solution :Relation(){privatevar numberOfPeople =0privateval cache = HashMap<Pair<Int, Int>, Boolean>()privatefuncachedKnows(a: Int, b: Int): Boolean {return cache.getOrPut(Pair(a, b)){knows(a, b)}}overridefunfindCelebrity(n: Int): Int {
numberOfPeople = n
var celebrityCandidate =0for(i in0 until n){if(cachedKnows(celebrityCandidate, i)){
celebrityCandidate = i
}}if(isCelebrity(celebrityCandidate)){return celebrityCandidate
}return-1}privatefunisCelebrity(i: Int): Boolean {for(j in0 until numberOfPeople){if(i == j)continueif(cachedKnows(i, j)||!cachedKnows(j, i)){returnfalse}}returntrue}}
/* The knows API is defined in the parent class Relation.
func knows(_ a: Int, _ b: Int) -> Bool */classSolution:Relation{privatevar numberOfPeople =0privatevar cache =[String:Bool]()privatefunccachedKnows(_ a:Int,_ b:Int)->Bool{let key ="\(a),\(b)"iflet cached = cache[key]{return cached
}let result =knows(a, b)
cache[key]= result
return result
}funcfindCelebrity(_ n:Int)->Int{
numberOfPeople = n
var celebrityCandidate =0for i in1..<n {ifcachedKnows(celebrityCandidate, i){
celebrityCandidate = i
}}ifisCelebrity(celebrityCandidate){return celebrityCandidate
}return-1}privatefuncisCelebrity(_ i:Int)->Bool{for j in0..<numberOfPeople {if i == j {continue}ifcachedKnows(i, j)||!cachedKnows(j, i){returnfalse}}returntrue}}
Time & Space Complexity
We don't know what time and space the knows(...) API uses. Because it's not our concern, we'll assume it's O(1) for the purpose of analysing our algorithm.
Time complexity: O(n)
Space complexity: O(n)
Where n is the number of nodes in the graph.
Common Pitfalls
Skipping the Verification Step
After finding a candidate using logical deduction, you must verify that the candidate truly knows nobody and is known by everyone. The candidate elimination only guarantees the candidate could be a celebrity, not that they definitely are one. Skipping verification can return false positives.
Starting the Candidate Search from Wrong Index
In the logical deduction approach, starting from candidate 0 and iterating from index 1 is intentional. Starting the loop from index 0 with knows(candidate, 0) would incorrectly update the candidate when comparing a person to themselves, since knows(0, 0) might return true.
Calling knows(i, i) During Verification
When verifying the candidate, always skip the case where i == j. Asking if someone knows themselves is undefined behavior in this problem and could return unexpected results that break your verification logic.