1719. Number Of Ways To Reconstruct A Tree
Problem Description
You're given an array pairs
where each element pairs[i] = [xi, yi]
represents a relationship between two nodes. The array has these properties:
- No duplicate pairs exist
- For each pair,
xi < yi
Your task is to determine how many different rooted trees can be constructed that satisfy these specific conditions:
-
Node composition: The tree must contain exactly the nodes that appear in the
pairs
array (no more, no less). -
Ancestor relationship rule: A pair
[xi, yi]
exists in the input if and only if one node is an ancestor of the other. This means:- If
[xi, yi]
is inpairs
, then eitherxi
must be an ancestor ofyi
, oryi
must be an ancestor ofxi
- If two nodes have an ancestor-descendant relationship in the tree, their pair must exist in
pairs
- If
-
Tree structure: The tree doesn't have to be binary - nodes can have any number of children.
Two tree configurations are considered different if at least one node has a different parent between the two trees.
Return values:
- Return
0
if no valid trees can be constructed (ways == 0
) - Return
1
if exactly one valid tree can be constructed (ways == 1
) - Return
2
if more than one valid tree can be constructed (ways > 1
)
Key definitions:
- A rooted tree has a single root node with all edges directed away from the root
- An ancestor of a node is any node on the path from the root to that node (not including the node itself)
- The root node has no ancestors
Intuition
The key insight is that if two nodes have an ancestor-descendant relationship in the tree, then one must be on the path from the root to the other. This means they must be connected through a chain of ancestor relationships with all intermediate nodes.
Think about what the pairs tell us: if [x, y]
is a pair, then in any valid tree, one must be an ancestor of the other. Now, if x
is an ancestor of y
, and we have another node z
where [x, z]
is also a pair, then either:
z
is betweenx
andy
on the path (making[y, z]
also a required pair)z
is on a different branch fromy
(no pair betweeny
andz
)
This leads to a crucial observation: nodes with more connections (higher degree) are more likely to be ancestors of nodes with fewer connections. Why? Because an ancestor node must have pairs with all its descendants, plus potentially its own ancestors.
The degree of a node in the pair relationships gives us a hint about its position in the tree hierarchy. A node with degree n-1
(connected to all other nodes) must be the root or very close to it, as it has an ancestor-descendant relationship with every other node.
To verify if a tree structure is valid, we can use a greedy approach:
- Sort nodes by their degree (number of connections)
- For each node, find a potential parent among nodes with higher or equal degree
- When we assign a parent
p
to nodex
, all nodes connected tox
must also be connected top
(transitivity of ancestor relationships)
The solution can have multiple valid trees when two nodes have the same degree - they could potentially swap positions in the hierarchy without violating the ancestor-descendant requirements. This is why we track if any two nodes have equal degrees.
If we can't find a valid parent for any node (except the root), or if we end up with multiple roots, then no valid tree exists.
Solution Approach
The implementation uses a greedy algorithm with graph representation to validate and count possible tree structures.
Data Structure Setup:
- Create a 2D boolean array
g[510][510]
to represent the adjacency relationships between nodes - Use a dictionary
v
to store the adjacency list for each node (neighbors of each node) - For each pair
[x, y]
, markg[x][y] = g[y][x] = True
and add them to each other's adjacency lists
Node Collection and Preprocessing:
nodes = []
for i in range(510):
if v[i]: # If node i exists in pairs
nodes.append(i)
g[i][i] = True # Mark self-connection for easier processing
Sorting by Degree: Sort all nodes by their degree (number of connections) in ascending order:
nodes.sort(key=lambda x: len(v[x]))
Greedy Parent Assignment:
For each node x
in sorted order:
-
Look for a potential parent among nodes with higher or equal degree:
j = i + 1 while j < len(nodes) and not g[x][nodes[j]]: j += 1
-
If a potential parent
y
is found:- Check if they have equal degrees:
len(v[x]) == len(v[y])
- If yes, set
equal = True
(multiple trees possible)
- If yes, set
- Verify transitivity: All neighbors of
x
must also be connected toy
for z in v[x]: if not g[y][z]: return 0 # Invalid [tree](/problems/tree_intro) structure
- Check if they have equal degrees:
-
If no parent is found, increment the root counter:
else: root += 1
Result Determination:
- If
root > 1
: Multiple roots exist, so no valid tree is possible → return0
- If
equal
isTrue
: At least two nodes had equal degrees, allowing position swaps → return2
- Otherwise: Exactly one valid tree structure exists → return
1
The algorithm efficiently validates the tree structure by ensuring:
- Each non-root node has exactly one valid parent
- The transitivity property holds (if
x
is ancestor ofy
andy
is ancestor ofz
, thenx
must be ancestor ofz
) - All required pairs are satisfied by the ancestor-descendant relationships
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with pairs = [[1,2], [2,3], [1,3]]
.
Step 1: Build the graph representation
- Create adjacency matrix
g
and adjacency listv
- From pairs:
g[1][2] = g[2][1] = True
,g[2][3] = g[3][2] = True
,g[1][3] = g[3][1] = True
- Adjacency lists:
v[1] = [2,3]
,v[2] = [1,3]
,v[3] = [1,2]
Step 2: Collect nodes and mark self-connections
- Nodes in the tree:
[1, 2, 3]
- Set
g[1][1] = g[2][2] = g[3][3] = True
Step 3: Sort nodes by degree
- Node degrees:
1
has degree 2,2
has degree 2,3
has degree 2 - After sorting:
nodes = [1, 2, 3]
(all have same degree)
Step 4: Greedy parent assignment
Processing node 1
(index 0):
- Look for parent starting from index 1 (node
2
) - Check if
g[1][2]
is True → Yes! - Node
2
has same degree as node1
→ Setequal = True
- Verify transitivity: All neighbors of
1
(which are[2,3]
) must connect to2
g[2][2]
= True ✓g[2][3]
= True ✓
- Parent found successfully
Processing node 2
(index 1):
- Look for parent starting from index 2 (node
3
) - Check if
g[2][3]
is True → Yes! - Node
3
has same degree as node2
→equal
remainsTrue
- Verify transitivity: All neighbors of
2
(which are[1,3]
) must connect to3
g[3][1]
= True ✓g[3][3]
= True ✓
- Parent found successfully
Processing node 3
(index 2):
- No more nodes to check for parent
- Increment
root = 1
(this is our root node)
Step 5: Determine result
root = 1
(exactly one root)equal = True
(nodes had equal degrees)- Return
2
(multiple valid trees possible)
Why multiple trees? Since all nodes have the same degree, we could have:
- Tree 1:
3
as root →2
as child →1
as child (chain: 3→2→1) - Tree 2:
3
as root →1
as child →2
as child (chain: 3→1→2) - Tree 3:
2
as root →3
as child →1
as child (chain: 2→3→1) - And so on...
Each configuration maintains all required ancestor-descendant relationships from the pairs.
Solution Implementation
1class Solution:
2 def checkWays(self, pairs: List[List[int]]) -> int:
3 # Create adjacency matrix to track connections between nodes
4 adjacency_matrix = [[False] * 510 for _ in range(510)]
5
6 # Create adjacency list to store neighbors for each node
7 adjacency_list = defaultdict(list)
8
9 # Build the graph from the given pairs
10 for node1, node2 in pairs:
11 adjacency_matrix[node1][node2] = adjacency_matrix[node2][node1] = True
12 adjacency_list[node1].append(node2)
13 adjacency_list[node2].append(node1)
14
15 # Collect all nodes that appear in the pairs
16 active_nodes = []
17 for node_id in range(510):
18 if adjacency_list[node_id]:
19 active_nodes.append(node_id)
20 # Mark self-connection as true for active nodes
21 adjacency_matrix[node_id][node_id] = True
22
23 # Sort nodes by their degree (number of connections) in ascending order
24 active_nodes.sort(key=lambda node: len(adjacency_list[node]))
25
26 # Track if there are multiple valid trees possible
27 multiple_solutions_exist = False
28
29 # Count potential root nodes
30 root_count = 0
31
32 # Check each node to find its potential parent in the tree
33 for current_index, current_node in enumerate(active_nodes):
34 # Look for the first node with higher or equal degree that is connected
35 parent_index = current_index + 1
36 while parent_index < len(active_nodes) and not adjacency_matrix[current_node][active_nodes[parent_index]]:
37 parent_index += 1
38
39 if parent_index < len(active_nodes):
40 # Found a potential parent node
41 parent_node = active_nodes[parent_index]
42
43 # Check if degrees are equal (multiple valid parent choices)
44 if len(adjacency_list[current_node]) == len(adjacency_list[parent_node]):
45 multiple_solutions_exist = True
46
47 # Verify that all neighbors of current node are also connected to parent
48 # This is necessary for a valid tree structure
49 for neighbor in adjacency_list[current_node]:
50 if not adjacency_matrix[parent_node][neighbor]:
51 return 0 # Invalid tree structure
52 else:
53 # No parent found, this node could be a root
54 root_count += 1
55
56 # A valid tree must have exactly one root
57 if root_count > 1:
58 return 0 # Invalid: multiple disconnected components
59
60 # Return 2 if multiple valid trees exist, 1 if unique tree exists
61 return 2 if multiple_solutions_exist else 1
62
1class Solution {
2 public int checkWays(int[][] pairs) {
3 // Create adjacency matrix to track connections between nodes
4 boolean[][] adjacencyMatrix = new boolean[510][510];
5
6 // Create adjacency list for each node
7 List<Integer>[] adjacencyList = new List[510];
8 Arrays.setAll(adjacencyList, k -> new ArrayList<>());
9
10 // Build the graph from input pairs
11 for (int[] pair : pairs) {
12 int nodeX = pair[0];
13 int nodeY = pair[1];
14
15 // Mark bidirectional connection in adjacency matrix
16 adjacencyMatrix[nodeX][nodeY] = true;
17 adjacencyMatrix[nodeY][nodeX] = true;
18
19 // Add to adjacency lists
20 adjacencyList[nodeX].add(nodeY);
21 adjacencyList[nodeY].add(nodeX);
22 }
23
24 // Collect all nodes that appear in the pairs
25 List<Integer> activeNodes = new ArrayList<>();
26 for (int nodeId = 0; nodeId < 510; ++nodeId) {
27 if (!adjacencyList[nodeId].isEmpty()) {
28 activeNodes.add(nodeId);
29 // Mark self-connection for active nodes
30 adjacencyMatrix[nodeId][nodeId] = true;
31 }
32 }
33
34 // Sort nodes by degree (number of connections) in ascending order
35 activeNodes.sort(Comparator.comparingInt(node -> adjacencyList[node].size()));
36
37 boolean hasEqualDegreeNodes = false;
38 int rootCount = 0;
39
40 // Process each node to validate tree structure
41 for (int i = 0; i < activeNodes.size(); ++i) {
42 int currentNode = activeNodes.get(i);
43
44 // Find the first node after current that is connected to it
45 int nextConnectedIndex = i + 1;
46 while (nextConnectedIndex < activeNodes.size() &&
47 !adjacencyMatrix[currentNode][activeNodes.get(nextConnectedIndex)]) {
48 nextConnectedIndex++;
49 }
50
51 if (nextConnectedIndex < activeNodes.size()) {
52 // Found a potential parent node
53 int parentNode = activeNodes.get(nextConnectedIndex);
54
55 // Check if current node and parent have same degree
56 if (adjacencyList[currentNode].size() == adjacencyList[parentNode].size()) {
57 hasEqualDegreeNodes = true;
58 }
59
60 // Verify that all neighbors of current node are also connected to parent
61 for (int neighbor : adjacencyList[currentNode]) {
62 if (!adjacencyMatrix[parentNode][neighbor]) {
63 // Invalid tree structure: parent should connect to all children's neighbors
64 return 0;
65 }
66 }
67 } else {
68 // No parent found, this is a root candidate
69 rootCount++;
70 }
71 }
72
73 // A valid tree should have exactly one root
74 if (rootCount > 1) {
75 return 0;
76 }
77
78 // Return 2 if multiple valid trees possible (due to equal degree nodes), otherwise 1
79 return hasEqualDegreeNodes ? 2 : 1;
80 }
81}
82
1class Solution {
2public:
3 int checkWays(vector<vector<int>>& pairs) {
4 // adjacency[i][j] indicates if node i and j have ancestor-descendant relationship
5 vector<vector<bool>> adjacency(510, vector<bool>(510));
6 // neighbors[i] stores all nodes that have ancestor-descendant relationship with node i
7 vector<vector<int>> neighbors(510);
8
9 // Build adjacency matrix and neighbor lists from input pairs
10 for (auto& pair : pairs) {
11 int nodeA = pair[0];
12 int nodeB = pair[1];
13 adjacency[nodeA][nodeB] = adjacency[nodeB][nodeA] = true;
14 neighbors[nodeA].push_back(nodeB);
15 neighbors[nodeB].push_back(nodeA);
16 }
17
18 // Collect all nodes that appear in the pairs
19 vector<int> activeNodes;
20 for (int nodeId = 1; nodeId <= 500; ++nodeId) {
21 if (!neighbors[nodeId].empty()) {
22 activeNodes.push_back(nodeId);
23 adjacency[nodeId][nodeId] = true; // Mark node as connected to itself
24 }
25 }
26
27 // Sort nodes by degree (number of neighbors) in ascending order
28 sort(activeNodes.begin(), activeNodes.end(),
29 [&](int a, int b) -> bool {
30 return neighbors[a].size() < neighbors[b].size();
31 });
32
33 bool hasEqualDegreeParent = false;
34 int rootCount = 0;
35
36 // Process each node to find its parent in the tree
37 for (int i = 0; i < activeNodes.size(); ++i) {
38 int currentNode = activeNodes[i];
39
40 // Find the first node with higher or equal degree that is connected to current node
41 int parentIndex = i + 1;
42 while (parentIndex < activeNodes.size() &&
43 !adjacency[currentNode][activeNodes[parentIndex]]) {
44 ++parentIndex;
45 }
46
47 if (parentIndex < activeNodes.size()) {
48 // Found a potential parent node
49 int parentNode = activeNodes[parentIndex];
50
51 // Check if parent has same degree (multiple valid trees possible)
52 if (neighbors[currentNode].size() == neighbors[parentNode].size()) {
53 hasEqualDegreeParent = true;
54 }
55
56 // Verify that all neighbors of current node are also connected to parent
57 // (required property for ancestor-descendant relationship)
58 for (int neighbor : neighbors[currentNode]) {
59 if (!adjacency[parentNode][neighbor]) {
60 return 0; // Invalid tree structure
61 }
62 }
63 } else {
64 // No parent found, this is a root node
65 ++rootCount;
66 }
67 }
68
69 // Check validity and uniqueness of the tree
70 if (rootCount > 1) {
71 return 0; // Multiple roots, invalid tree
72 }
73 if (hasEqualDegreeParent) {
74 return 2; // Multiple valid trees possible
75 }
76 return 1; // Exactly one valid tree
77 }
78};
79
1function checkWays(pairs: number[][]): number {
2 // adjacency[i][j] indicates if node i and j have ancestor-descendant relationship
3 const adjacency: boolean[][] = Array(510).fill(null).map(() => Array(510).fill(false));
4 // neighbors[i] stores all nodes that have ancestor-descendant relationship with node i
5 const neighbors: number[][] = Array(510).fill(null).map(() => []);
6
7 // Build adjacency matrix and neighbor lists from input pairs
8 for (const pair of pairs) {
9 const nodeA = pair[0];
10 const nodeB = pair[1];
11 adjacency[nodeA][nodeB] = adjacency[nodeB][nodeA] = true;
12 neighbors[nodeA].push(nodeB);
13 neighbors[nodeB].push(nodeA);
14 }
15
16 // Collect all nodes that appear in the pairs
17 const activeNodes: number[] = [];
18 for (let nodeId = 1; nodeId <= 500; nodeId++) {
19 if (neighbors[nodeId].length > 0) {
20 activeNodes.push(nodeId);
21 adjacency[nodeId][nodeId] = true; // Mark node as connected to itself
22 }
23 }
24
25 // Sort nodes by degree (number of neighbors) in ascending order
26 activeNodes.sort((a, b) => neighbors[a].length - neighbors[b].length);
27
28 let hasEqualDegreeParent = false;
29 let rootCount = 0;
30
31 // Process each node to find its parent in the tree
32 for (let i = 0; i < activeNodes.length; i++) {
33 const currentNode = activeNodes[i];
34
35 // Find the first node with higher or equal degree that is connected to current node
36 let parentIndex = i + 1;
37 while (parentIndex < activeNodes.length &&
38 !adjacency[currentNode][activeNodes[parentIndex]]) {
39 parentIndex++;
40 }
41
42 if (parentIndex < activeNodes.length) {
43 // Found a potential parent node
44 const parentNode = activeNodes[parentIndex];
45
46 // Check if parent has same degree (multiple valid trees possible)
47 if (neighbors[currentNode].length === neighbors[parentNode].length) {
48 hasEqualDegreeParent = true;
49 }
50
51 // Verify that all neighbors of current node are also connected to parent
52 // (required property for ancestor-descendant relationship)
53 for (const neighbor of neighbors[currentNode]) {
54 if (!adjacency[parentNode][neighbor]) {
55 return 0; // Invalid tree structure
56 }
57 }
58 } else {
59 // No parent found, this is a root node
60 rootCount++;
61 }
62 }
63
64 // Check validity and uniqueness of the tree
65 if (rootCount > 1) {
66 return 0; // Multiple roots, invalid tree
67 }
68 if (hasEqualDegreeParent) {
69 return 2; // Multiple valid trees possible
70 }
71 return 1; // Exactly one valid tree
72}
73
Time and Space Complexity
Time Complexity: O(n² + m·n)
where n
is the number of unique nodes and m
is the number of pairs.
- Building the adjacency matrix
g
and adjacency listv
from pairs takesO(m)
time - Extracting unique nodes by iterating through 510 positions takes
O(510) = O(1)
time - Sorting nodes by degree takes
O(n log n)
time - The main loop iterates through each node (
O(n)
):- Finding the next connected node with higher degree: worst case
O(n)
iterations - Checking if all neighbors of
x
are connected toy
:O(degree(x))
which can beO(n)
in worst case
- Finding the next connected node with higher degree: worst case
- Overall:
O(m) + O(n log n) + O(n²·n) = O(n³)
in worst case, but more preciselyO(n² + m·n)
considering the actual graph structure
Space Complexity: O(510² + n + m) = O(1)
since 510 is a constant
- The adjacency matrix
g
uses510 × 510 = O(1)
space (fixed size) - The adjacency list
v
stores at mostO(m)
edges across all nodes - The
nodes
list stores at mostO(n)
unique nodes - Since the maximum node value is bounded by 510, the space is effectively
O(1)
constant space
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Ancestor-Descendant Requirement
Pitfall: A common mistake is thinking that the pairs represent direct parent-child relationships. In reality, pairs represent ANY ancestor-descendant relationship, not just immediate parent-child connections.
Example: If we have pairs [[1,2], [1,3], [2,3]]
, node 1 could be the root with node 2 as its child, and node 3 as the child of node 2. The pair [1,3]
exists because 1 is an ancestor of 3 (grandparent relationship).
Solution: When verifying tree validity, ensure that ALL descendants of a node are connected to its ancestors in the adjacency matrix, not just direct children.
2. Incorrect Handling of Equal Degree Nodes
Pitfall: When two nodes have the same degree and are connected, the algorithm marks multiple_solutions_exist = True
. However, developers often forget that this only applies when one could be the parent of the other, not when they're siblings.
Example: If nodes A and B both have degree 3 and are connected, they could potentially swap positions in the tree hierarchy, but only if one can be the ancestor of the other while maintaining all required connections.
Solution: The check for equal degrees should only trigger the multiple solutions flag when the node with equal degree is actually chosen as the parent:
if parent_index < len(active_nodes):
parent_node = active_nodes[parent_index]
# Only mark multiple solutions if this parent has equal degree
if len(adjacency_list[current_node]) == len(adjacency_list[parent_node]):
multiple_solutions_exist = True
3. Forgetting the Transitivity Check
Pitfall: Not verifying that all neighbors of a child node are also connected to its chosen parent. This transitivity property is crucial for maintaining the ancestor-descendant relationships.
Example: If node X has neighbors [Y, Z] and we assign P as X's parent, then P must also be connected to both Y and Z. Otherwise, the ancestor relationships would be violated.
Solution: Always validate transitivity when assigning a parent:
for neighbor in adjacency_list[current_node]: if not adjacency_matrix[parent_node][neighbor]: return 0 # Invalid tree structure
4. Inefficient Node Range Iteration
Pitfall: Using a fixed range of 510 nodes when the actual node values might be sparse or have a different range. This can lead to unnecessary memory usage and potential index out of bounds errors.
Solution: Dynamically determine the range of nodes or use a more flexible data structure:
# Better approach: determine max node value first
max_node = max(max(pair) for pair in pairs) if pairs else 0
adjacency_matrix = [[False] * (max_node + 1) for _ in range(max_node + 1)]
# Or use a dictionary-based approach for sparse graphs
adjacency_matrix = defaultdict(lambda: defaultdict(bool))
5. Not Handling Edge Cases
Pitfall: Failing to handle special cases like empty input, single pair, or when all nodes are fully connected.
Solution: Add explicit checks for edge cases:
def checkWays(self, pairs: List[List[int]]) -> int:
if not pairs:
return 1 # Empty tree is valid
if len(pairs) == 1:
return 1 # Single edge forms a valid tree
# Continue with main algorithm...
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
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 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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!