851. Loud and Rich
Problem Description
You have n
people labeled from 0
to n - 1
. Each person has:
- A different amount of money
- A different quietness level (given in the
quiet
array wherequiet[i]
represents the quietness of personi
)
You're given a richer
array where each element richer[i] = [a, b]
means person a
has more money than person b
. The relationships are logically consistent (no circular contradictions).
For each person x
, you need to find the quietest person among all people who have equal to or more money than person x
. This includes:
- Person
x
themselves - Anyone directly richer than person
x
- Anyone richer than those people (transitively richer)
Return an array answer
where answer[x]
is the person with the smallest quietness value among all people who are at least as rich as person x
.
Example Understanding:
- If person A is richer than person B, and person C is richer than person A, then both A and C are considered richer than B
- When finding
answer[B]
, you need to consider B, A, C, and anyone else who is richer, then return whoever has the minimumquiet
value among them
The solution uses DFS with memoization to traverse the "richer than" relationships. It builds a graph where edges point from less wealthy to more wealthy people, then for each person, finds the quietest person among all reachable nodes (people with more money).
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves relationships between people where "richer than" relationships can be modeled as directed edges. Each person is a node, and
richer[i] = [a, b]
means there's an edge fromb
toa
(persona
is richer than personb
).
Is it a tree?
- No: The graph is not necessarily a tree. Multiple people can be richer than the same person (multiple parents), and there can be multiple disconnected components.
Is the problem related to directed acyclic graphs?
- Yes: The problem explicitly states the data is "logically correct" with no circular contradictions, meaning if A is richer than B, and B is richer than C, then C cannot be richer than A. This forms a DAG (Directed Acyclic Graph).
Topological Sort
- While topological sort could be used here, the problem is more about traversing from each node to find all reachable nodes (people with more money) and tracking the minimum quietness value. This is better suited for DFS with memoization.
However, the core pattern here is DFS because:
- We need to explore all paths from each person to find everyone richer than them
- We can use memoization to avoid recalculating results for the same person
- DFS naturally handles the transitive relationships (if A > B and B > C, we need to consider A when looking at C)
Conclusion: The flowchart leads us to use DFS to traverse the directed acyclic graph of "richer than" relationships. For each person, we perform DFS to find all people with equal or more money, then return the one with minimum quietness value. The memoization optimization prevents redundant traversals.
Intuition
The key insight is to think about the "richer than" relationships as forming a directed graph. If person A is richer than person B, we can draw an arrow from B to A. This creates a hierarchy of wealth.
For each person, we need to find the quietest person among all those who are at least as rich. This means we need to:
- Start from a person
- Find all people reachable by following the "richer than" arrows
- Among all these people (including the starting person), find who has the minimum quietness value
The natural approach is to use DFS to explore all reachable nodes from each person. However, if we do this naively, we might traverse the same paths multiple times. For example, if both person A and person B are richer than person C, when we compute answers for both A and B, we'd traverse from C repeatedly.
This is where memoization comes in. Once we've computed the answer for a person (the quietest among all richer people), we can store and reuse it. The recursive DFS naturally builds up answers from the "richest" people down to the "poorest" ones.
The graph construction is clever: instead of edges from richer to poorer (A β B if A > B), we build edges from poorer to richer (B β A if A > B). This way, when we DFS from any person, we naturally traverse "upward" to all richer people.
The algorithm works because:
- The DAG property ensures no infinite loops
- DFS explores all paths to richer people
- Memoization ensures each person's answer is computed only once
- We compare quietness values at each step to maintain the minimum
Learn more about Depth-First Search, Graph and Topological Sort patterns.
Solution Approach
The implementation follows a DFS with memoization pattern using the following components:
1. Graph Construction: First, we build an adjacency list where edges point from poorer to richer people:
g = defaultdict(list)
for a, b in richer:
g[b].append(a) # b β a means a is richer than b
This reversed direction allows us to traverse "upward" from any person to find all richer people.
2. Memoization Array:
We initialize an answer array with -1
to track unvisited nodes:
ans = [-1] * n
This serves as both our memoization cache and visited marker.
3. DFS Function: The recursive DFS function processes each person:
def dfs(i: int):
if ans[i] != -1: # Already computed
return
ans[i] = i # Initially, person i is their own answer
for j in g[i]: # Explore all richer people
dfs(j) # Ensure j's answer is computed
if quiet[ans[j]] < quiet[ans[i]]:
ans[i] = ans[j] # Update if we found someone quieter
Algorithm Flow:
- Base Case: If we've already computed the answer for person
i
(ans[i] != -1), return immediately - Initialize: Set
ans[i] = i
, assuming the person themselves is the quietest initially - Explore: For each person
j
who is richer thani
:- Recursively compute
ans[j]
first - Compare
quiet[ans[j]]
withquiet[ans[i]]
- If person
ans[j]
is quieter, updateans[i] = ans[j]
- Recursively compute
4. Main Loop: Process all people to ensure everyone's answer is computed:
for i in range(n):
dfs(i)
Why This Works:
- The DFS explores all paths to richer people, ensuring we consider everyone with at least as much money
- Memoization prevents redundant computation - once we know the quietest rich person for someone, we reuse that result
- The recursive nature naturally handles transitive relationships (if A > B > C, when computing for C, we'll reach A through B)
- By comparing
quiet[ans[j]]
instead ofquiet[j]
, we leverage previously computed optimal answers
Time Complexity: O(n + m) where n is the number of people and m is the number of relationships, as each person and edge is processed once due to memoization.
Space Complexity: O(n + m) for the graph storage and recursion stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input:
n = 4
(people labeled 0, 1, 2, 3)quiet = [3, 2, 5, 4]
(quietness levels)richer = [[1, 0], [2, 1], [3, 1]]
(wealth relationships)
This means:
- Person 1 is richer than Person 0
- Person 2 is richer than Person 1
- Person 3 is richer than Person 1
Step 1: Build the Graph We create edges from poorer to richer:
g[0] = [1] (1 is richer than 0) g[1] = [2, 3] (2 and 3 are richer than 1) g[2] = [] (no one explicitly richer than 2) g[3] = [] (no one explicitly richer than 3)
Step 2: Initialize
ans = [-1, -1, -1, -1]
Step 3: DFS Traversal
Let's trace through dfs(0)
:
-
ans[0] = 0
(initially assume person 0 is the answer) -
Explore richer people:
g[0] = [1]
, so calldfs(1)
Within
dfs(1)
:-
ans[1] = 1
(initially) -
Explore
g[1] = [2, 3]
, so calldfs(2)
Within
dfs(2)
:ans[2] = 2
(no richer people to explore)- Return
-
Back in
dfs(1)
, compare:quiet[ans[2]] = quiet[2] = 5
vsquiet[ans[1]] = quiet[1] = 2
-
Since 2 < 5, keep
ans[1] = 1
-
Now call
dfs(3)
Within
dfs(3)
:ans[3] = 3
(no richer people to explore)- Return
-
Back in
dfs(1)
, compare:quiet[ans[3]] = quiet[3] = 4
vsquiet[ans[1]] = quiet[1] = 2
-
Since 2 < 4, keep
ans[1] = 1
-
Return
-
-
Back in
dfs(0)
, compare:quiet[ans[1]] = quiet[1] = 2
vsquiet[ans[0]] = quiet[0] = 3
-
Since 2 < 3, update
ans[0] = 1
After processing all people:
ans[0] = 1
(among {0,1,2,3}, person 1 has min quiet value 2)ans[1] = 1
(among {1,2,3}, person 1 has min quiet value 2)ans[2] = 2
(only person 2, quiet value 5)ans[3] = 3
(only person 3, quiet value 4)
Key Observations:
- Person 0 can reach all richer people (1, 2, 3) through the graph
- The DFS explores the entire reachable subgraph for each starting person
- Memoization prevents recomputing: when we later call
dfs(1)
directly, it returns immediately sinceans[1] != -1
- The algorithm correctly identifies that person 1 is the quietest among all people richer than or equal to person 0
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def loudAndRich(self, richer: List[List[int]], quiet: List[int]) -> List[int]:
6 """
7 Find the quietest person among all people richer than or equal to each person.
8
9 Args:
10 richer: List of [a, b] pairs where person a has more money than person b
11 quiet: List where quiet[i] represents the quietness level of person i
12
13 Returns:
14 List where result[i] is the quietest person among all richer or equal to person i
15 """
16
17 def dfs(person: int) -> None:
18 """
19 Depth-first search to find the quietest person among all richer people.
20 Uses memoization to avoid redundant calculations.
21
22 Args:
23 person: The current person being processed
24 """
25 # If already computed, return early (memoization)
26 if result[person] != -1:
27 return
28
29 # Initially, the person themselves is the quietest they know
30 result[person] = person
31
32 # Check all people who are richer than the current person
33 for richer_person in graph[person]:
34 # Recursively compute the answer for the richer person
35 dfs(richer_person)
36
37 # Update if we found someone quieter through the richer person's connections
38 if quiet[result[richer_person]] < quiet[result[person]]:
39 result[person] = result[richer_person]
40
41 # Build adjacency list: graph[person] contains all people richer than person
42 graph = defaultdict(list)
43 for richer_person, poorer_person in richer:
44 graph[poorer_person].append(richer_person)
45
46 # Initialize result array with -1 (unprocessed marker)
47 num_people = len(quiet)
48 result = [-1] * num_people
49
50 # Process each person to find their quietest richer person
51 for person in range(num_people):
52 dfs(person)
53
54 return result
55
1class Solution {
2 // Graph adjacency list where g[i] contains all people richer than person i
3 private List<Integer>[] graph;
4 // Total number of people
5 private int numberOfPeople;
6 // Array storing quietness values for each person
7 private int[] quietnessValues;
8 // Array storing the answer - quietest person among all richer or equally rich people
9 private int[] result;
10
11 public int[] loudAndRich(int[][] richer, int[] quiet) {
12 // Initialize instance variables
13 numberOfPeople = quiet.length;
14 this.quietnessValues = quiet;
15 graph = new List[numberOfPeople];
16 result = new int[numberOfPeople];
17
18 // Initialize result array with -1 to indicate unvisited nodes
19 Arrays.fill(result, -1);
20
21 // Initialize adjacency list for each person
22 Arrays.setAll(graph, index -> new ArrayList<>());
23
24 // Build the graph: if person a is richer than person b (richer[i] = [a, b])
25 // then add a to the adjacency list of b
26 for (int[] richRelation : richer) {
27 int richerPerson = richRelation[0];
28 int poorerPerson = richRelation[1];
29 graph[poorerPerson].add(richerPerson);
30 }
31
32 // Perform DFS for each person to find their quietest richer person
33 for (int person = 0; person < numberOfPeople; person++) {
34 dfs(person);
35 }
36
37 return result;
38 }
39
40 /**
41 * Depth-first search to find the quietest person among all people
42 * who are richer than or equal to person i
43 * @param currentPerson the person we're currently processing
44 */
45 private void dfs(int currentPerson) {
46 // If already computed, return early (memoization)
47 if (result[currentPerson] != -1) {
48 return;
49 }
50
51 // Initially, the quietest person is the current person themselves
52 result[currentPerson] = currentPerson;
53
54 // Explore all people richer than the current person
55 for (int richerPerson : graph[currentPerson]) {
56 // Recursively compute the quietest person for the richer person
57 dfs(richerPerson);
58
59 // Update if we found a quieter person through the richer person's network
60 if (quietnessValues[result[richerPerson]] < quietnessValues[result[currentPerson]]) {
61 result[currentPerson] = result[richerPerson];
62 }
63 }
64 }
65}
66
1class Solution {
2public:
3 vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
4 int n = quiet.size();
5
6 // Build adjacency list: graph[i] contains all people richer than person i
7 vector<vector<int>> graph(n);
8 for (auto& richPair : richer) {
9 int richerPerson = richPair[0];
10 int poorerPerson = richPair[1];
11 graph[poorerPerson].push_back(richerPerson);
12 }
13
14 // answer[i] will store the quietest person among all people richer than or equal to i
15 vector<int> answer(n, -1);
16
17 // DFS function to find the quietest person for each node
18 function<void(int)> dfs = [&](int person) {
19 // If already computed, return early (memoization)
20 if (answer[person] != -1) {
21 return;
22 }
23
24 // Initially, the person themselves is the quietest
25 answer[person] = person;
26
27 // Check all people richer than the current person
28 for (int richerPerson : graph[person]) {
29 // Recursively compute answer for the richer person
30 dfs(richerPerson);
31
32 // Update if we found someone quieter
33 if (quiet[answer[richerPerson]] < quiet[answer[person]]) {
34 answer[person] = answer[richerPerson];
35 }
36 }
37 };
38
39 // Compute answer for each person
40 for (int i = 0; i < n; ++i) {
41 dfs(i);
42 }
43
44 return answer;
45 }
46};
47
1/**
2 * Finds the quietest person among all people who have equal or more money than each person
3 * @param richer - Array of [a, b] pairs where person a has more money than person b
4 * @param quiet - Array where quiet[i] represents the quietness level of person i
5 * @returns Array where result[i] is the quietest person among all richer or equal people to person i
6 */
7function loudAndRich(richer: number[][], quiet: number[]): number[] {
8 const numberOfPeople: number = quiet.length;
9
10 // Build adjacency list where graph[i] contains all people richer than person i
11 const richerThanGraph: number[][] = new Array(numberOfPeople)
12 .fill(0)
13 .map(() => []);
14
15 // Populate the graph with richer relationships
16 for (const [richerPerson, poorerPerson] of richer) {
17 richerThanGraph[poorerPerson].push(richerPerson);
18 }
19
20 // Initialize result array with -1 to indicate unprocessed persons
21 const quietestRicherPerson: number[] = new Array(numberOfPeople).fill(-1);
22
23 /**
24 * Depth-first search to find the quietest person among all people
25 * who are richer than or equal to the given person
26 * @param personIndex - The index of the current person being processed
27 */
28 const findQuietestRicherPerson = (personIndex: number): void => {
29 // If already computed, return early
30 if (quietestRicherPerson[personIndex] !== -1) {
31 return;
32 }
33
34 // Initially, the person themselves is the quietest they know
35 quietestRicherPerson[personIndex] = personIndex;
36
37 // Check all people richer than the current person
38 for (const richerPersonIndex of richerThanGraph[personIndex]) {
39 // Recursively find the quietest person for each richer person
40 findQuietestRicherPerson(richerPersonIndex);
41
42 // Update if we found someone quieter
43 if (quiet[quietestRicherPerson[richerPersonIndex]] < quiet[quietestRicherPerson[personIndex]]) {
44 quietestRicherPerson[personIndex] = quietestRicherPerson[richerPersonIndex];
45 }
46 }
47 };
48
49 // Process each person to find their quietest richer person
50 for (let personIndex = 0; personIndex < numberOfPeople; personIndex++) {
51 findQuietestRicherPerson(personIndex);
52 }
53
54 return quietestRicherPerson;
55}
56
Time and Space Complexity
Time Complexity: O(n + m)
where n
is the number of people and m
is the number of relationships in the richer
array.
- Building the adjacency list takes
O(m)
time as we iterate through all relationships once. - The DFS traversal visits each person at most once due to memoization (checking
if ans[i] != -1
). For each person, we explore all their edges in the graph. - Since each person is processed once and each edge is traversed once during the entire algorithm, the total time is
O(n + m)
.
Space Complexity: O(n + m)
- The adjacency list
g
stores all edges, requiringO(m)
space. - The
ans
array stores results forn
people, requiringO(n)
space. - The recursive call stack can go up to
O(n)
deep in the worst case (when the graph forms a chain). - Total space complexity is
O(n + m)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Graph Direction Confusion
One of the most common mistakes is building the graph in the wrong direction. Since we need to find people who are richer than a given person, many developers intuitively build edges from richer to poorer:
Incorrect approach:
# Wrong direction - edges from richer to poorer
graph = defaultdict(list)
for richer_person, poorer_person in richer:
graph[richer_person].append(poorer_person) # WRONG!
This makes it impossible to traverse from a person to find all people richer than them. You'd need to traverse the entire graph to find who points to a given person.
Correct approach:
# Correct direction - edges from poorer to richer
graph = defaultdict(list)
for richer_person, poorer_person in richer:
graph[poorer_person].append(richer_person) # Correct
2. Comparing Wrong Values
Another frequent error is directly comparing the quietness of the richer person instead of their computed answer:
Incorrect comparison:
for richer_person in graph[person]: dfs(richer_person) # Wrong - comparing the richer person directly if quiet[richer_person] < quiet[result[person]]: result[person] = richer_person # WRONG!
This fails because richer_person
might not be the quietest among all people richer than them. We need to use their already-computed optimal answer.
Correct comparison:
for richer_person in graph[person]: dfs(richer_person) # Correct - comparing the computed answer for the richer person if quiet[result[richer_person]] < quiet[result[person]]: result[person] = result[richer_person] # Correct
3. Forgetting Self-Initialization
Failing to initialize each person as their own answer can lead to incorrect results:
Missing initialization:
def dfs(person):
if result[person] != -1:
return
# Missing: result[person] = person
for richer_person in graph[person]:
# ... rest of logic
Without this initialization, if a person has no one richer than them, their answer would remain -1 or could be incorrectly set.
4. Not Handling Isolated Nodes
Some people might not appear in the richer
array at all (they're neither richer nor poorer than anyone else in the given relationships). The code must handle these cases by ensuring every person is processed:
Incomplete processing:
# Wrong - only processing people who appear in relationships
visited = set()
for a, b in richer:
if a not in visited:
dfs(a)
visited.add(a)
if b not in visited:
dfs(b)
visited.add(b)
Complete processing:
# Correct - ensuring all n people are processed
for person in range(n):
dfs(person)
5. Infinite Recursion Without Memoization Check
Forgetting the memoization check at the beginning of DFS can cause infinite recursion if there are complex relationship chains:
Missing memoization:
def dfs(person):
# Missing: if result[person] != -1: return
result[person] = person
for richer_person in graph[person]:
dfs(richer_person) # Could revisit already processed nodes
# ...
The memoization check is crucial for both correctness and performance, preventing redundant calculations and potential stack overflow.
Which data structure is used to implement priority queue?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Topological Sort Topological Order Prereq Breadth First Search Review problems graph_bfs Directed Graph Before we get started on topological order let's talk about directed graphs first A graph is directed when its edges have directions these edges are also called arcs Suppose that v w are vertices in a directed
Want a Structured Path to Master System Design Too? Donβt Miss This!