Facebook Pixel

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 where quiet[i] represents the quietness of person i)

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 minimum quiet 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 from b to a (person a is richer than person b).

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Start from a person
  2. Find all people reachable by following the "richer than" arrows
  3. 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:

  1. Base Case: If we've already computed the answer for person i (ans[i] != -1), return immediately
  2. Initialize: Set ans[i] = i, assuming the person themselves is the quietest initially
  3. Explore: For each person j who is richer than i:
    • Recursively compute ans[j] first
    • Compare quiet[ans[j]] with quiet[ans[i]]
    • If person ans[j] is quieter, update ans[i] = ans[j]

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 of quiet[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 Evaluator

Example 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):

  1. ans[0] = 0 (initially assume person 0 is the answer)

  2. Explore richer people: g[0] = [1], so call dfs(1)

    Within dfs(1):

    • ans[1] = 1 (initially)

    • Explore g[1] = [2, 3], so call dfs(2)

      Within dfs(2):

      • ans[2] = 2 (no richer people to explore)
      • Return
    • Back in dfs(1), compare: quiet[ans[2]] = quiet[2] = 5 vs quiet[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 vs quiet[ans[1]] = quiet[1] = 2

    • Since 2 < 4, keep ans[1] = 1

    • Return

  3. Back in dfs(0), compare: quiet[ans[1]] = quiet[1] = 2 vs quiet[ans[0]] = quiet[0] = 3

  4. 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 since ans[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, requiring O(m) space.
  • The ans array stores results for n people, requiring O(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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More