LeetCode 1600 - Throne Inheritance

The problem asks us to design a data structure that simulates a royal inheritance system. There is a king at the top of the family tree, and over time people can be born or die. We must support three operations efficiently: 1. Add a child to an existing person. 2.

LeetCode Problem 1600

Difficulty: 🟡 Medium
Topics: Hash Table, Tree, Depth-First Search, Design

Solution

Problem Understanding

The problem asks us to design a data structure that simulates a royal inheritance system. There is a king at the top of the family tree, and over time people can be born or die. We must support three operations efficiently:

  1. Add a child to an existing person.
  2. Mark a person as dead.
  3. Return the current inheritance order.

The inheritance order follows a preorder traversal of the family tree. A parent always appears before their children, and among siblings, older children appear before younger children. Since births are added chronologically, the order in which children are inserted matters.

An important detail is that death does not remove a person from the family tree. Dead people still affect traversal order because their descendants must remain in the correct position. The only difference is that dead people are skipped when producing the final inheritance list.

The recursive Successor definition in the statement effectively describes a preorder depth first traversal:

  1. Visit the current person.
  2. Recursively visit each child from oldest to youngest.

The input operations dynamically modify the family tree. The birth operation adds a directed parent to child relationship. The death operation simply marks a person as dead. The getInheritanceOrder operation must return all living people in the correct inheritance order.

The constraints are very important:

  • Up to 10^5 calls can be made to birth and death.
  • Only up to 10 calls are made to getInheritanceOrder.

This tells us that updates happen much more frequently than queries. That observation strongly influences the design choice. We do not want to rebuild expensive structures after every birth or death. Instead, we should store the family tree incrementally and generate the inheritance order only when requested.

Several edge cases are important:

  • The king may die, but their descendants still inherit in the same order.
  • A dead person's children must still appear in the inheritance list.
  • A person may have many children, and birth order must remain preserved.
  • The inheritance order query may happen after many updates.
  • The tree can become very deep, so recursive traversal should still remain correct within practical limits.

Approaches

Brute Force Approach

A brute force approach would attempt to explicitly maintain the inheritance order after every operation.

For example, after every birth or death, we could rebuild the entire inheritance sequence by simulating the Successor process from scratch. This would involve traversing the whole family tree repeatedly.

The approach is correct because preorder traversal naturally matches the inheritance rules. However, rebuilding the entire order after every update is expensive.

If there are N people and we rebuild the order after every operation, the total cost can become O(N^2) across many operations.

Since there can be up to 10^5 updates, repeatedly recomputing the entire order is inefficient.

Optimal Approach

The key insight is that births and deaths only modify local information:

  • Birth adds a child to a parent's children list.
  • Death marks a person as deceased.

Neither operation requires recomputing the inheritance order immediately.

Because getInheritanceOrder is called at most 10 times, we can defer all expensive work until query time. We store:

  • An adjacency list representing the family tree.
  • A set containing dead people.
  • The king's name.

When getInheritanceOrder is called, we perform a preorder DFS traversal:

  1. Visit the current person.
  2. Add them to the result if alive.
  3. Recursively process children in birth order.

This directly mirrors the inheritance rules.

Approach Time Complexity Space Complexity Notes
Brute Force O(N) per update O(N) Rebuilds inheritance order repeatedly
Optimal Birth: O(1), Death: O(1), Query: O(N) O(N) Stores tree and computes order only when needed

Algorithm Walkthrough

Optimal Algorithm

  1. Initialize the data structure with the king's name.

We store the king separately because traversal always starts from the king. We also create:

  • A hash map from parent to list of children.
  • A set of dead people.
  1. Handle births by appending the child to the parent's child list.

The order of insertion matters because inheritance follows oldest to youngest child order. Appending naturally preserves this ordering. 3. Handle deaths by adding the person's name to the dead set.

We do not remove them from the tree because their descendants still inherit in the same location. 4. When getInheritanceOrder is called, start a preorder DFS from the king.

The traversal follows the inheritance rules exactly:

  • Process the current person first.
  • Then recursively process children from oldest to youngest.
  1. During DFS, add only living people to the result.

Dead people are skipped, but traversal continues through their descendants. 6. Return the completed result list.

Why it works

The inheritance rules are equivalent to preorder traversal of the family tree. Every parent appears before descendants, and siblings are visited in birth order. Since births append children in chronological order, the DFS traversal naturally produces the correct inheritance sequence. Skipping dead people while continuing traversal preserves the required ordering of descendants.

Python Solution

from collections import defaultdict
from typing import List

class ThroneInheritance:

    def __init__(self, kingName: str):
        self.king = kingName
        self.children = defaultdict(list)
        self.dead = set()

    def birth(self, parentName: str, childName: str) -> None:
        self.children[parentName].append(childName)

    def death(self, name: str) -> None:
        self.dead.add(name)

    def getInheritanceOrder(self) -> List[str]:
        order = []

        def dfs(person: str) -> None:
            if person not in self.dead:
                order.append(person)

            for child in self.children[person]:
                dfs(child)

        dfs(self.king)
        return order

The implementation stores the royal family as a tree using an adjacency list. The children dictionary maps each person to their list of children in birth order.

The constructor initializes the king, the adjacency list, and the dead set.

The birth method appends the child to the parent's child list. Since births occur chronologically, appending preserves oldest to youngest ordering automatically.

The death method adds the person to the dead set. We intentionally keep dead people in the tree because their descendants still belong in the inheritance order.

The getInheritanceOrder method performs a preorder DFS traversal. If the current person is alive, they are added to the result. Then all children are recursively processed in order.

This implementation directly mirrors the inheritance definition from the problem statement.

Go Solution

package main

type ThroneInheritance struct {
	king     string
	children map[string][]string
	dead     map[string]bool
}

func Constructor(kingName string) ThroneInheritance {
	return ThroneInheritance{
		king:     kingName,
		children: make(map[string][]string),
		dead:     make(map[string]bool),
	}
}

func (this *ThroneInheritance) Birth(parentName string, childName string) {
	this.children[parentName] = append(this.children[parentName], childName)
}

func (this *ThroneInheritance) Death(name string) {
	this.dead[name] = true
}

func (this *ThroneInheritance) GetInheritanceOrder() []string {
	order := []string{}

	var dfs func(string)

	dfs = func(person string) {
		if !this.dead[person] {
			order = append(order, person)
		}

		for _, child := range this.children[person] {
			dfs(child)
		}
	}

	dfs(this.king)

	return order
}

The Go implementation follows the same logic as the Python version.

The children structure uses a map from string to slice of strings. Slices naturally preserve insertion order, which is important for maintaining birth order.

The dead structure is implemented as a map[string]bool because Go does not have a built in set type.

The DFS traversal is implemented using a nested function closure. The recursion appends living people to the result slice while continuing traversal through all descendants.

Worked Examples

Example 1

Input:

["ThroneInheritance", "birth", "birth", "birth", "birth", "birth", "birth", "getInheritanceOrder", "death", "getInheritanceOrder"]

Step by Step State

Operation Children Map Dead Set Result
Initialize king {} {}
birth("king", "andy") king -> [andy] {}
birth("king", "bob") king -> [andy, bob] {}
birth("king", "catherine") king -> [andy, bob, catherine] {}
birth("andy", "matthew") andy -> [matthew] {}
birth("bob", "alex") bob -> [alex] {}
birth("bob", "asha") bob -> [alex, asha] {}

Now we call getInheritanceOrder().

DFS Traversal

Current Person Alive? Current Result
king Yes [king]
andy Yes [king, andy]
matthew Yes [king, andy, matthew]
bob Yes [king, andy, matthew, bob]
alex Yes [king, andy, matthew, bob, alex]
asha Yes [king, andy, matthew, bob, alex, asha]
catherine Yes [king, andy, matthew, bob, alex, asha, catherine]

Returned result:

["king", "andy", "matthew", "bob", "alex", "asha", "catherine"]

Now death("bob") is called.

Operation Dead Set
death("bob") {bob}

Call getInheritanceOrder() again.

DFS Traversal After Death

Current Person Alive? Current Result
king Yes [king]
andy Yes [king, andy]
matthew Yes [king, andy, matthew]
bob No [king, andy, matthew]
alex Yes [king, andy, matthew, alex]
asha Yes [king, andy, matthew, alex, asha]
catherine Yes [king, andy, matthew, alex, asha, catherine]

Returned result:

["king", "andy", "matthew", "alex", "asha", "catherine"]

Complexity Analysis

Measure Complexity Explanation
Time Birth: O(1), Death: O(1), GetInheritanceOrder: O(N) DFS visits every person once
Space O(N) Stores family tree and dead set

The birth operation is constant time because appending to a child list is O(1) amortized. The death operation is also constant time because hash set insertion is O(1) average case.

The getInheritanceOrder operation performs a preorder traversal over the entire family tree. Every person is visited exactly once, so the complexity is O(N).

The space complexity is O(N) because we store all people in the adjacency list and possibly in the dead set.

Test Cases

from typing import List
from collections import defaultdict

class ThroneInheritance:

    def __init__(self, kingName: str):
        self.king = kingName
        self.children = defaultdict(list)
        self.dead = set()

    def birth(self, parentName: str, childName: str) -> None:
        self.children[parentName].append(childName)

    def death(self, name: str) -> None:
        self.dead.add(name)

    def getInheritanceOrder(self) -> List[str]:
        result = []

        def dfs(person: str):
            if person not in self.dead:
                result.append(person)

            for child in self.children[person]:
                dfs(child)

        dfs(self.king)

        return result

# Example from problem statement
t = ThroneInheritance("king")
t.birth("king", "andy")
t.birth("king", "bob")
t.birth("king", "catherine")
t.birth("andy", "matthew")
t.birth("bob", "alex")
t.birth("bob", "asha")

assert t.getInheritanceOrder() == [
    "king",
    "andy",
    "matthew",
    "bob",
    "alex",
    "asha",
    "catherine"
]  # standard preorder inheritance

t.death("bob")

assert t.getInheritanceOrder() == [
    "king",
    "andy",
    "matthew",
    "alex",
    "asha",
    "catherine"
]  # dead person removed but descendants preserved

# Only king exists
t2 = ThroneInheritance("king")

assert t2.getInheritanceOrder() == ["king"]  # minimal case

# King dies
t3 = ThroneInheritance("king")
t3.birth("king", "a")
t3.birth("a", "b")
t3.death("king")

assert t3.getInheritanceOrder() == ["a", "b"]  # dead king excluded

# Deep inheritance chain
t4 = ThroneInheritance("k")
t4.birth("k", "a")
t4.birth("a", "b")
t4.birth("b", "c")
t4.birth("c", "d")

assert t4.getInheritanceOrder() == [
    "k", "a", "b", "c", "d"
]  # deep DFS traversal

# Dead person with living descendants
t5 = ThroneInheritance("k")
t5.birth("k", "a")
t5.birth("a", "b")
t5.birth("b", "c")

t5.death("a")

assert t5.getInheritanceOrder() == [
    "k", "b", "c"
]  # descendants still appear

# Multiple siblings preserve birth order
t6 = ThroneInheritance("k")
t6.birth("k", "a")
t6.birth("k", "b")
t6.birth("k", "c")

assert t6.getInheritanceOrder() == [
    "k", "a", "b", "c"
]  # sibling order preserved

# Multiple deaths
t7 = ThroneInheritance("k")
t7.birth("k", "a")
t7.birth("k", "b")
t7.birth("a", "c")

t7.death("a")
t7.death("b")

assert t7.getInheritanceOrder() == [
    "k", "c"
]  # multiple dead nodes skipped
Test Why
Problem statement example Validates standard preorder inheritance
Only king exists Tests smallest possible tree
King dies Ensures dead root is skipped correctly
Deep inheritance chain Verifies recursive DFS traversal
Dead person with descendants Confirms descendants remain reachable
Multiple siblings Ensures birth order preservation
Multiple deaths Tests skipping several dead people

Edge Cases

The king dies

One subtle case is when the king is marked as dead. A naive implementation might incorrectly remove the entire subtree rooted at the king. That would be wrong because descendants still inherit.

This implementation handles the case correctly because death only adds the king to the dead set. During DFS traversal, the king is skipped in the output, but traversal still continues through all descendants.

A dead person has living children

Another important edge case occurs when someone dies after having children. Their descendants must still appear in the inheritance order exactly where they normally would.

The DFS traversal naturally handles this. The dead person is skipped when building the result list, but recursion still processes all children. This preserves the inheritance structure correctly.

Large number of birth operations

With up to 10^5 operations, repeatedly recomputing inheritance order after every birth would be too slow.

This implementation avoids that issue by storing births incrementally in an adjacency list. Each birth only appends to a list in constant time. Expensive traversal is deferred until getInheritanceOrder is actually requested, which happens at most 10 times.