DP on Trees Diameter of Tree in DSA Typescript - Time & Space Complexity
We want to understand how long it takes to find the longest path in a tree using dynamic programming.
How does the time grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
function treeDiameter(adj: number[][]): number {
let diameter = 0;
function dfs(node: number, parent: number): number {
let max1 = 0, max2 = 0;
for (const child of adj[node]) {
if (child === parent) continue;
const length = dfs(child, node);
if (length > max1) {
max2 = max1;
max1 = length;
} else if (length > max2) {
max2 = length;
}
}
diameter = Math.max(diameter, max1 + max2);
return max1 + 1;
}
dfs(0, -1);
return diameter;
}
This code finds the longest path (diameter) in a tree using a depth-first search and dynamic programming.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The recursive depth-first search (dfs) visits each node once.
- How many times: Each node and its edges are processed exactly once during the recursion.
As the number of nodes grows, the function visits each node and edge once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 nodes and their edges visited once each |
| 100 | About 100 nodes and their edges visited once each |
| 1000 | About 1000 nodes and their edges visited once each |
Pattern observation: The work grows roughly in direct proportion to the number of nodes.
Time Complexity: O(n)
This means the time to find the diameter grows linearly with the number of nodes in the tree.
[X] Wrong: "The diameter calculation takes quadratic time because it checks all pairs of nodes."
[OK] Correct: The code uses a single depth-first search that visits each node once, avoiding repeated checks of node pairs.
Understanding this linear time approach shows you can handle tree problems efficiently, a key skill in many coding challenges.
"What if the tree was represented as an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of diameter
The diameter is the longest path between any two nodes in a tree, not just from the root.Step 2: Differentiate from other tree properties
Height is the longest path from root to leaf, number of nodes and leaves are counts, not paths.Final Answer:
The longest path between any two nodes in the tree -> Option AQuick Check:
Diameter = Longest path [OK]
- Confusing diameter with height
- Thinking diameter counts nodes instead of path length
- Assuming diameter is from root only
Solution
Step 1: Identify tree representation needs
We need to store connections between nodes, so adjacency list (array of edges) is suitable.Step 2: Eliminate unsuitable types
String array, number variable, or boolean flag cannot represent node connections effectively.Final Answer:
Array of edges (adjacency list) -> Option DQuick Check:
Adjacency list = Array of edges [OK]
- Using simple variables instead of lists
- Confusing data types with tree structure
- Using string arrays for numeric node connections
const tree = {
0: [1, 2],
1: [0, 3],
2: [0],
3: [1]
};
function dfs(node: number, parent: number): number {
let max1 = 0, max2 = 0;
for (const child of tree[node]) {
if (child === parent) continue;
const dist = dfs(child, node) + 1;
if (dist > max1) {
max2 = max1;
max1 = dist;
} else if (dist > max2) {
max2 = dist;
}
}
diameter = Math.max(diameter, max1 + max2);
return max1;
}
let diameter = 0;
dfs(0, -1);
console.log(diameter);Solution
Step 1: Trace DFS from node 0
From node 0, children are 1 and 2. DFS(1) returns 2 (path 1->3), DFS(2) returns 1. dist from 1: 3, from 2: 2.Step 2: Calculate diameter updates
At node 0, max1=2, max2=1, diameter updated to 3 (2+1). No larger path found elsewhere.Final Answer:
3 -> Option BQuick Check:
Longest path length = 3 [OK]
- Ignoring parent to avoid cycles
- Not updating diameter with sum of two longest paths
- Returning wrong distance from DFS
const tree = {
0: [1],
1: [0, 2],
2: [1]
};
let diameter = 0;
function dfs(node: number): number {
let max1 = 0, max2 = 0;
for (const child of tree[node]) {
const dist = dfs(child) + 1;
if (dist > max1) {
max2 = max1;
max1 = dist;
} else if (dist > max2) {
max2 = dist;
}
}
diameter = Math.max(diameter, max1 + max2);
return max1;
}
dfs(0);
console.log(diameter);Solution
Step 1: Identify infinite recursion cause
DFS revisits parent node because no parent check, causing infinite recursion.Step 2: Fix by adding parent parameter
Pass parent node to DFS and skip it in children loop to avoid cycles.Final Answer:
Missing parent parameter to avoid revisiting nodes -> Option AQuick Check:
Parent check prevents infinite recursion [OK]
- Not skipping parent node in DFS
- Confusing diameter initialization
- Returning wrong values from DFS
const tree = {
0: [{node:1, weight:3}, {node:2, weight:2}],
1: [{node:0, weight:3}, {node:3, weight:4}],
2: [{node:0, weight:2}],
3: [{node:1, weight:4}]
};Solution
Step 1: Incorporate weights in DFS distance calculation
Instead of adding 1 for each edge, add the edge's weight to the distance.Step 2: Update diameter with sum of two longest weighted paths
Keep track of two longest weighted distances from children and update diameter with their sum.Final Answer:
Add edge weights to distances during DFS and update diameter accordingly -> Option CQuick Check:
Weighted distances sum = diameter [OK]
- Ignoring weights and counting edges only
- Using BFS without weights
- Counting nodes instead of path weights
Ready to prove it?
Pass the test and this pattern moves unknown
