1993. Operations on Tree
Problem Description
You need to implement a data structure for a tree that supports locking operations. The tree has n
nodes numbered from 0
to n - 1
, where node 0
is the root. The tree structure is given as a parent array where parent[i]
indicates the parent of node i
. Since node 0
is the root, parent[0] = -1
.
The data structure must support three operations:
1. Lock Operation (lock(num, user)
)
- Locks node
num
for useruser
- Returns
true
if locking is successful,false
otherwise - A node can only be locked if it's currently unlocked
- Once locked by a user, other users cannot lock the same node
2. Unlock Operation (unlock(num, user)
)
- Unlocks node
num
for useruser
- Returns
true
if unlocking is successful,false
otherwise - A node can only be unlocked by the same user who locked it
3. Upgrade Operation (upgrade(num, user)
)
- Locks node
num
for useruser
AND unlocks all its descendants - Returns
true
if upgrade is successful,false
otherwise - The upgrade can only happen when ALL three conditions are met:
- The node
num
itself is unlocked - At least one descendant of
num
is locked (by any user) - No ancestor of
num
is locked
- The node
The solution uses:
self.locked
: An array tracking which user locked each node (-1
means unlocked)self.parent
: The given parent arrayself.children
: A list of children for each node (built from the parent array)
The upgrade
operation uses DFS to traverse and unlock all descendants, while also checking if any descendants are locked. It traverses up the tree to verify no ancestors are locked before performing the upgrade.
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 a tree structure with nodes and edges (parent-child relationships). A tree is a special type of graph.
Is it a tree?
- Yes: The problem explicitly states we're working with a tree structure. We have
n
nodes with parent-child relationships defined by the parent array, and node 0 is the root.
DFS
- Conclusion: According to the flowchart, when we have a tree structure, the appropriate algorithm pattern is DFS (Depth-First Search).
Why DFS is the Right Choice
The flowchart correctly identifies DFS as the solution pattern because:
-
Tree Traversal: The
upgrade
operation requires traversing all descendants of a given node to unlock them. DFS is perfect for exploring all nodes in a subtree. -
Recursive Nature: The tree structure naturally lends itself to recursive traversal, which is exactly what DFS provides. In the solution, the
dfs
function recursively visits each child and their descendants. -
Complete Subtree Exploration: The upgrade operation needs to:
- Check if any descendant is locked (requires visiting all descendants)
- Unlock all locked descendants (requires accessing every node in the subtree)
DFS ensures we visit every node in the subtree exactly once, making it efficient for this operation.
-
Parent-Child Relationships: The solution builds a
children
array to facilitate DFS traversal from any node downward through its descendants, which is essential for the upgrade operation.
The DFS pattern in the solution is implemented in the dfs
helper function within the upgrade
method, which recursively traverses all children and their descendants to perform the required unlocking operations.
Intuition
The key insight is recognizing that we need to efficiently track lock states and navigate both upward and downward in the tree structure.
For the basic lock
and unlock
operations, we simply need to track who has locked each node. A simple array where locked[i]
stores the user ID (or -1
if unlocked) handles this elegantly.
The challenging part is the upgrade
operation, which has three distinct requirements that need different traversal strategies:
-
Check no ancestors are locked: We need to traverse upward from the node to the root. Since we have the parent array, we can simply follow
parent[x]
repeatedly until we reach the root (parent[x] = -1
). If we find any locked node along this path, the upgrade fails. -
Check at least one descendant is locked AND unlock all descendants: These two requirements can be handled together with a single downward traversal. This is where DFS shines - we recursively visit all children and their descendants. During this traversal, we:
- Track if we found any locked descendant using a flag variable
- Unlock any locked nodes we encounter
The beauty of this approach is that we combine the checking and unlocking in a single DFS pass, making it efficient.
To enable downward traversal, we preprocess the parent array to build a children
list for each node. This one-time preprocessing in the constructor allows us to quickly access all children of any node during DFS.
The solution elegantly uses:
- Upward traversal (following parent pointers) for ancestor checking
- Downward traversal (DFS through children) for descendant operations
- State tracking with the
locked
array for O(1) lock status checks
This combination of bidirectional tree navigation and simple state management provides an efficient solution to all three operations.
Learn more about Tree, Depth-First Search and Breadth-First Search patterns.
Solution Approach
Let's walk through the implementation of each component and operation in the LockingTree
class:
Data Structure Initialization
The constructor sets up three key data structures:
-
self.locked
: An array of sizen
initialized with-1
, wherelocked[i]
stores the user ID who locked nodei
, or-1
if unlocked. -
self.parent
: Stores the given parent array for upward traversal. -
self.children
: A list of lists built from the parent array. For each nodei
,children[i]
contains all direct children of nodei
. This is constructed by iterating through the parent array starting from index 1 (skipping root) and appending each node to its parent's children list.
for son, fa in enumerate(parent[1:], 1):
self.children[fa].append(son)
Lock Operation
The lock
method is straightforward:
- Check if
locked[num] == -1
(node is unlocked) - If yes, set
locked[num] = user
and returnTrue
- Otherwise, return
False
Time complexity: O(1)
Unlock Operation
The unlock
method verifies ownership:
- Check if
locked[num] == user
(same user who locked it) - If yes, set
locked[num] = -1
and returnTrue
- Otherwise, return
False
Time complexity: O(1)
Upgrade Operation
The upgrade
method is the most complex, implementing three checks:
Step 1: Check no ancestors are locked
x = num while x != -1: if self.locked[x] != -1: return False x = self.parent[x]
Starting from num
, traverse upward to the root. If any ancestor is locked, immediately return False
.
Step 2: Find and unlock all locked descendants
def dfs(x: int):
nonlocal find
for y in self.children[x]:
if self.locked[y] != -1:
self.locked[y] = -1
find = True
dfs(y)
The DFS helper function:
- Recursively visits all children and their descendants
- When a locked node is found, unlocks it (
locked[y] = -1
) and setsfind = True
- The
nonlocal find
allows the nested function to modify the outer scope'sfind
variable
Step 3: Finalize the upgrade
find = False dfs(num) if not find: return False self.locked[num] = user return True
- Initialize
find
toFalse
before DFS - Run DFS to unlock descendants and detect if any were locked
- If no locked descendants were found (
find
is stillFalse
), returnFalse
- Otherwise, lock the node for the user and return
True
Time Complexity Analysis
- Lock/Unlock: O(1) - Simple array access and update
- Upgrade: O(h + d) where
h
is the height from node to root (for ancestor check) andd
is the number of descendants (for DFS traversal)
The solution efficiently handles all operations by maintaining proper data structures for both upward and downward tree traversal, making it practical even for large trees.
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 concrete example with a small tree to illustrate how the locking operations work.
Tree Structure:
parent = [-1, 0, 0, 1, 1, 3] Tree visualization: 0 / \ 1 2 / \ 3 4 / 5
Initial Setup:
locked = [-1, -1, -1, -1, -1, -1]
(all nodes unlocked)children = [[1,2], [3,4], [], [5], [], []]
Operation Sequence:
-
lock(1, 10)
→ Returnstrue
- Check:
locked[1] == -1
? Yes, node 1 is unlocked - Action: Set
locked[1] = 10
- State:
locked = [-1, 10, -1, -1, -1, -1]
- Check:
-
lock(3, 20)
→ Returnstrue
- Check:
locked[3] == -1
? Yes, node 3 is unlocked - Action: Set
locked[3] = 20
- State:
locked = [-1, 10, -1, 20, -1, -1]
- Check:
-
unlock(3, 10)
→ Returnsfalse
- Check:
locked[3] == 10
? No, it's locked by user 20 - Action: No change
- State:
locked = [-1, 10, -1, 20, -1, -1]
- Check:
-
upgrade(0, 30)
→ Returnsfalse
- Check ancestors of node 0: Node 0 has no ancestors (it's the root)
- Check descendants: Found locked nodes 1 and 3
- But node 0's direct child (node 1) is locked, violating the "node itself must be unlocked" condition
- Action: No change
-
unlock(1, 10)
→ Returnstrue
- Check:
locked[1] == 10
? Yes - Action: Set
locked[1] = -1
- State:
locked = [-1, -1, -1, 20, -1, -1]
- Check:
-
upgrade(1, 40)
→ Returnstrue
- Check ancestors: Traverse 1→0, check if any are locked
locked[1] == -1
? Yes (unlocked)locked[0] == -1
? Yes (unlocked)
- Check descendants via DFS from node 1:
- Visit child 3:
locked[3] == 20
(locked!) → Unlock it, setfind = true
- Visit child 4:
locked[4] == -1
(already unlocked) - Visit grandchild 5:
locked[5] == -1
(already unlocked)
- Visit child 3:
- Since
find == true
(found at least one locked descendant), proceed - Action: Lock node 1 for user 40
- Final state:
locked = [-1, 40, -1, -1, -1, -1]
- Check ancestors: Traverse 1→0, check if any are locked
-
upgrade(3, 50)
→ Returnsfalse
- Check ancestors: Traverse 3→1→0
locked[3] == -1
? Yes (unlocked)locked[1] == 40
? No! Node 1 is locked
- Since an ancestor is locked, immediately return
false
- Action: No change
- Check ancestors: Traverse 3→1→0
This example demonstrates all three operations and shows how the upgrade operation carefully checks both ancestors (upward traversal) and descendants (DFS traversal) while managing the lock states.
Solution Implementation
1from typing import List
2
3class LockingTree:
4 def __init__(self, parent: List[int]):
5 """
6 Initialize the locking tree with parent relationships.
7
8 Args:
9 parent: List where parent[i] is the parent of node i, parent[0] = -1 (root)
10 """
11 n = len(parent)
12 # Store which user locked each node (-1 means unlocked)
13 self.locked = [-1] * n
14 # Store parent of each node
15 self.parent = parent
16 # Build adjacency list for children of each node
17 self.children = [[] for _ in range(n)]
18
19 # Populate children list (skip root at index 0)
20 for child_node, parent_node in enumerate(parent[1:], 1):
21 self.children[parent_node].append(child_node)
22
23 def lock(self, num: int, user: int) -> bool:
24 """
25 Lock a node if it's currently unlocked.
26
27 Args:
28 num: Node to lock
29 user: User attempting to lock
30
31 Returns:
32 True if lock successful, False otherwise
33 """
34 if self.locked[num] == -1: # Node is unlocked
35 self.locked[num] = user
36 return True
37 return False
38
39 def unlock(self, num: int, user: int) -> bool:
40 """
41 Unlock a node if it's locked by the same user.
42
43 Args:
44 num: Node to unlock
45 user: User attempting to unlock
46
47 Returns:
48 True if unlock successful, False otherwise
49 """
50 if self.locked[num] == user: # Node is locked by this user
51 self.locked[num] = -1
52 return True
53 return False
54
55 def upgrade(self, num: int, user: int) -> bool:
56 """
57 Upgrade a node by locking it and unlocking all descendants if:
58 1. The node and all ancestors are unlocked
59 2. At least one descendant is locked
60
61 Args:
62 num: Node to upgrade
63 user: User attempting to upgrade
64
65 Returns:
66 True if upgrade successful, False otherwise
67 """
68 # Check if any ancestor (including self) is locked
69 current_node = num
70 while current_node != -1:
71 if self.locked[current_node] != -1: # Found a locked ancestor
72 return False
73 current_node = self.parent[current_node]
74
75 # Check and unlock all locked descendants
76 has_locked_descendant = False
77
78 def unlock_descendants(node: int) -> None:
79 """DFS to unlock all descendants and track if any were locked."""
80 nonlocal has_locked_descendant
81
82 for child in self.children[node]:
83 if self.locked[child] != -1: # Child is locked
84 self.locked[child] = -1 # Unlock it
85 has_locked_descendant = True
86 unlock_descendants(child) # Continue to child's descendants
87
88 unlock_descendants(num)
89
90 # If no locked descendants found, upgrade fails
91 if not has_locked_descendant:
92 return False
93
94 # Lock the current node with the user
95 self.locked[num] = user
96 return True
97
98
99# Your LockingTree object will be instantiated and called as such:
100# obj = LockingTree(parent)
101# param_1 = obj.lock(num, user)
102# param_2 = obj.unlock(num, user)
103# param_3 = obj.upgrade(num, user)
104
1class LockingTree {
2 // Array to store which user has locked each node (-1 means unlocked)
3 private int[] lockedBy;
4 // Array to store parent of each node
5 private int[] parent;
6 // Array of lists to store children of each node
7 private List<Integer>[] children;
8
9 /**
10 * Initialize the locking tree with parent relationships
11 * @param parent Array where parent[i] is the parent of node i
12 */
13 public LockingTree(int[] parent) {
14 int n = parent.length;
15
16 // Initialize arrays
17 this.lockedBy = new int[n];
18 this.parent = parent;
19 this.children = new List[n];
20
21 // Initially all nodes are unlocked
22 Arrays.fill(this.lockedBy, -1);
23
24 // Initialize children lists for each node
25 Arrays.setAll(this.children, i -> new ArrayList<>());
26
27 // Build children relationships from parent array
28 for (int i = 1; i < n; i++) {
29 this.children[parent[i]].add(i);
30 }
31 }
32
33 /**
34 * Lock a node by a user
35 * @param num The node to lock
36 * @param user The user attempting to lock
37 * @return true if lock successful, false if already locked
38 */
39 public boolean lock(int num, int user) {
40 // Check if node is unlocked
41 if (lockedBy[num] == -1) {
42 lockedBy[num] = user;
43 return true;
44 }
45 return false;
46 }
47
48 /**
49 * Unlock a node by a user
50 * @param num The node to unlock
51 * @param user The user attempting to unlock
52 * @return true if unlock successful, false if not locked by this user
53 */
54 public boolean unlock(int num, int user) {
55 // Check if node is locked by the requesting user
56 if (lockedBy[num] == user) {
57 lockedBy[num] = -1;
58 return true;
59 }
60 return false;
61 }
62
63 /**
64 * Upgrade a node: lock it and unlock all descendants
65 * Conditions: node and ancestors must be unlocked, at least one descendant must be locked
66 * @param num The node to upgrade
67 * @param user The user attempting to upgrade
68 * @return true if upgrade successful, false otherwise
69 */
70 public boolean upgrade(int num, int user) {
71 // Check if the node and all its ancestors are unlocked
72 int currentNode = num;
73 while (currentNode != -1) {
74 if (lockedBy[currentNode] != -1) {
75 return false;
76 }
77 currentNode = parent[currentNode];
78 }
79
80 // Check for locked descendants and unlock them
81 boolean[] hasLockedDescendant = new boolean[1];
82 unlockDescendants(num, hasLockedDescendant);
83
84 // If no locked descendants found, upgrade fails
85 if (!hasLockedDescendant[0]) {
86 return false;
87 }
88
89 // Lock the current node
90 lockedBy[num] = user;
91 return true;
92 }
93
94 /**
95 * DFS to unlock all descendants and check if any were locked
96 * @param node Current node in traversal
97 * @param hasLockedDescendant Flag to track if any descendant was locked
98 */
99 private void unlockDescendants(int node, boolean[] hasLockedDescendant) {
100 // Process all children
101 for (int child : children[node]) {
102 // If child is locked, unlock it and set flag
103 if (lockedBy[child] != -1) {
104 lockedBy[child] = -1;
105 hasLockedDescendant[0] = true;
106 }
107 // Recursively process descendants
108 unlockDescendants(child, hasLockedDescendant);
109 }
110 }
111}
112
113/**
114 * Your LockingTree object will be instantiated and called as such:
115 * LockingTree obj = new LockingTree(parent);
116 * boolean param_1 = obj.lock(num,user);
117 * boolean param_2 = obj.unlock(num,user);
118 * boolean param_3 = obj.upgrade(num,user);
119 */
120
1class LockingTree {
2private:
3 vector<int> lockedBy; // lockedBy[i] = user who locked node i (-1 if unlocked)
4 vector<int> parentNode; // parentNode[i] = parent of node i
5 vector<vector<int>> childNodes; // childNodes[i] = list of children of node i
6
7public:
8 LockingTree(vector<int>& parent) {
9 int nodeCount = parent.size();
10
11 // Initialize data structures
12 lockedBy = vector<int>(nodeCount, -1);
13 parentNode = parent;
14 childNodes.resize(nodeCount);
15
16 // Build adjacency list for children
17 for (int i = 1; i < nodeCount; ++i) {
18 childNodes[parent[i]].push_back(i);
19 }
20 }
21
22 bool lock(int num, int user) {
23 // Can only lock if node is currently unlocked
24 if (lockedBy[num] == -1) {
25 lockedBy[num] = user;
26 return true;
27 }
28 return false;
29 }
30
31 bool unlock(int num, int user) {
32 // Can only unlock if node is locked by the same user
33 if (lockedBy[num] == user) {
34 lockedBy[num] = -1;
35 return true;
36 }
37 return false;
38 }
39
40 bool upgrade(int num, int user) {
41 // Check condition 1: node must be unlocked
42 if (lockedBy[num] != -1) {
43 return false;
44 }
45
46 // Check condition 2: all ancestors must be unlocked
47 int currentNode = num;
48 while (currentNode != -1) {
49 if (lockedBy[currentNode] != -1) {
50 return false;
51 }
52 currentNode = parentNode[currentNode];
53 }
54
55 // Check condition 3: at least one descendant must be locked
56 // Also unlock all locked descendants
57 bool hasLockedDescendant = false;
58
59 function<void(int)> unlockDescendants = [&](int node) {
60 // Process all children of current node
61 for (int child : childNodes[node]) {
62 // If child is locked, unlock it and mark that we found a locked descendant
63 if (lockedBy[child] != -1) {
64 hasLockedDescendant = true;
65 lockedBy[child] = -1;
66 }
67 // Recursively process child's descendants
68 unlockDescendants(child);
69 }
70 };
71
72 unlockDescendants(num);
73
74 // If no locked descendant found, upgrade fails
75 if (!hasLockedDescendant) {
76 return false;
77 }
78
79 // All conditions met: lock the node with the given user
80 lockedBy[num] = user;
81 return true;
82 }
83};
84
85/**
86 * Your LockingTree object will be instantiated and called as such:
87 * LockingTree* obj = new LockingTree(parent);
88 * bool param_1 = obj->lock(num,user);
89 * bool param_2 = obj->unlock(num,user);
90 * bool param_3 = obj->upgrade(num,user);
91 */
92
1// Array to store which user has locked each node (-1 means unlocked)
2let locked: number[];
3// Array to store parent of each node
4let parentArray: number[];
5// Array to store children of each node
6let childrenArray: number[][];
7
8/**
9 * Initializes the locking tree with parent relationships
10 * @param parent - Array where parent[i] is the parent of node i
11 */
12function initialize(parent: number[]): void {
13 const nodeCount = parent.length;
14
15 // Initialize locked array with -1 (unlocked state)
16 locked = Array(nodeCount).fill(-1);
17
18 // Store parent relationships
19 parentArray = parent;
20
21 // Initialize children array as empty arrays
22 childrenArray = Array(nodeCount)
23 .fill(0)
24 .map(() => []);
25
26 // Build children relationships from parent array
27 for (let i = 1; i < nodeCount; i++) {
28 childrenArray[parent[i]].push(i);
29 }
30}
31
32/**
33 * Attempts to lock a node for a specific user
34 * @param num - The node to lock
35 * @param user - The user attempting to lock
36 * @returns true if lock successful, false otherwise
37 */
38function lock(num: number, user: number): boolean {
39 // Check if node is currently unlocked
40 if (locked[num] === -1) {
41 locked[num] = user;
42 return true;
43 }
44 return false;
45}
46
47/**
48 * Attempts to unlock a node for a specific user
49 * @param num - The node to unlock
50 * @param user - The user attempting to unlock
51 * @returns true if unlock successful, false otherwise
52 */
53function unlock(num: number, user: number): boolean {
54 // Check if node is locked by the same user
55 if (locked[num] === user) {
56 locked[num] = -1;
57 return true;
58 }
59 return false;
60}
61
62/**
63 * Attempts to upgrade a node's lock status
64 * Upgrade succeeds if:
65 * 1. The node and all ancestors are unlocked
66 * 2. At least one descendant is locked
67 * 3. All locked descendants get unlocked and the node gets locked
68 * @param num - The node to upgrade
69 * @param user - The user attempting to upgrade
70 * @returns true if upgrade successful, false otherwise
71 */
72function upgrade(num: number, user: number): boolean {
73 // Check if node and all its ancestors are unlocked
74 let currentNode = num;
75 while (currentNode !== -1) {
76 if (locked[currentNode] !== -1) {
77 return false;
78 }
79 currentNode = parentArray[currentNode];
80 }
81
82 // Track if any descendant is locked
83 let hasLockedDescendant = false;
84
85 /**
86 * DFS to unlock all descendants and check if any were locked
87 * @param nodeIndex - Current node being processed
88 */
89 const unlockDescendants = (nodeIndex: number): void => {
90 for (const childNode of childrenArray[nodeIndex]) {
91 // If child is locked, unlock it and mark that we found a locked descendant
92 if (locked[childNode] !== -1) {
93 locked[childNode] = -1;
94 hasLockedDescendant = true;
95 }
96 // Recursively process all descendants
97 unlockDescendants(childNode);
98 }
99 };
100
101 // Unlock all descendants and check if any were locked
102 unlockDescendants(num);
103
104 // If no descendants were locked, upgrade fails
105 if (!hasLockedDescendant) {
106 return false;
107 }
108
109 // Lock the node for the user
110 locked[num] = user;
111 return true;
112}
113
Time and Space Complexity
Time Complexity:
-
__init__(parent)
:O(n)
wheren
is the length of the parent array. We iterate through the parent array once to build the children adjacency list. -
lock(num, user)
:O(1)
. We only check and update a single element in the locked array. -
unlock(num, user)
:O(1)
. We only check and update a single element in the locked array. -
upgrade(num, user)
:O(n)
in the worst case.- Checking ancestors:
O(h)
whereh
is the height of the tree. In the worst case (skewed tree),h = n - 1
. - DFS to unlock descendants:
O(m)
wherem
is the number of nodes in the subtree rooted atnum
. In the worst case, this could beO(n)
if most nodes are descendants. - Overall:
O(h + m) = O(n)
in the worst case.
- Checking ancestors:
Space Complexity:
-
__init__(parent)
:O(n)
for storing:self.locked
:O(n)
arrayself.parent
:O(n)
arrayself.children
:O(n)
for the adjacency list (total edges =n - 1
)
-
lock(num, user)
:O(1)
. No additional space used. -
unlock(num, user)
:O(1)
. No additional space used. -
upgrade(num, user)
:O(h)
whereh
is the height of the tree, due to the recursive call stack in the DFS function. In the worst case (skewed tree), this could beO(n)
.
Overall Space Complexity of the class: O(n)
for the data structures maintained throughout the object's lifetime.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Ancestor Check in Upgrade Operation
A critical pitfall is checking ancestors incorrectly during the upgrade operation. Many implementations mistakenly start checking from the parent of the target node instead of the node itself.
Incorrect Implementation:
def upgrade(self, num: int, user: int) -> bool:
# WRONG: Starting from parent, missing the check for num itself
current_node = self.parent[num] # Skips checking if num is locked!
while current_node != -1:
if self.locked[current_node] != -1:
return False
current_node = self.parent[current_node]
Why This Fails:
According to the problem requirements, the upgrade operation requires that "the node num
itself is unlocked." If we start checking from the parent, we miss verifying whether the target node is already locked, which would violate the upgrade conditions.
Correct Implementation:
def upgrade(self, num: int, user: int) -> bool:
# CORRECT: Start from the node itself
current_node = num # Include num in the ancestor check
while current_node != -1:
if self.locked[current_node] != -1:
return False
current_node = self.parent[current_node]
2. Forgetting to Use nonlocal
in Nested DFS Function
When using a nested function to track whether any descendants are locked, forgetting the nonlocal
keyword causes the variable update to be local to the nested function only.
Incorrect Implementation:
def upgrade(self, num: int, user: int) -> bool:
has_locked_descendant = False
def unlock_descendants(node: int) -> None:
# WRONG: Missing nonlocal, creates a new local variable
for child in self.children[node]:
if self.locked[child] != -1:
self.locked[child] = -1
has_locked_descendant = True # This creates a LOCAL variable!
unlock_descendants(child)
unlock_descendants(num)
# has_locked_descendant is still False here, even if descendants were locked
if not has_locked_descendant:
return False
Correct Implementation:
def upgrade(self, num: int, user: int) -> bool:
has_locked_descendant = False
def unlock_descendants(node: int) -> None:
nonlocal has_locked_descendant # CORRECT: Modifies outer scope variable
for child in self.children[node]:
if self.locked[child] != -1:
self.locked[child] = -1
has_locked_descendant = True
unlock_descendants(child)
3. Inefficient Children List Construction
Building the children adjacency list incorrectly can lead to index errors or missing relationships.
Incorrect Implementation:
def __init__(self, parent: List[int]):
self.children = [[] for _ in range(len(parent))]
# WRONG: Starting from 0 includes root, which has parent -1
for child_node, parent_node in enumerate(parent):
if parent_node != -1: # Extra check needed
self.children[parent_node].append(child_node)
Better Implementation:
def __init__(self, parent: List[int]):
self.children = [[] for _ in range(len(parent))]
# CORRECT: Start from index 1, skip root automatically
for child_node, parent_node in enumerate(parent[1:], 1):
self.children[parent_node].append(child_node)
The second approach is cleaner as it naturally skips the root node (which has no parent) and avoids the need for conditional checks.
4. Not Handling Edge Cases Properly
Failing to consider edge cases like single-node trees or operations on the root node.
Example Edge Case:
- Tree with only root node (n=1): The upgrade operation should always fail since root has no descendants
- Attempting to upgrade the root when it's the only locked node in the tree
Solution: The implementation should naturally handle these cases through the DFS traversal, which will find no descendants for a leaf node, causing the upgrade to correctly return False
.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
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 bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!