Reference: LeetCode
Difficulty: Easy

Problem

Given a binary tree, find the length of the longest path where each node in the path has the same value. This path may or may not pass through the root.

The length of path between two nodes is represented by the number of edges between them.

Note: The given binary tree has not more than 10000 nodes. The height of the tree is not more than 1000.

Example:

1
2
3
4
5
6
7
Input:
5
/ \
4 5
/ \ \
1 1 5
Output: 2
1
2
3
4
5
6
7
Input:
1
/ \
4 5
/ \ \
4 4 5
Output: 2

Analysis

Note that the following 0-1-2-3 is not a path (degree should be less than or equal to 2). In other words, a path can only be a list.

1
2
3
4
5
    0
/
1
/ \
2 3

Methods:

  1. Recursion
    • We need a lengthHelper to return the max value on one side. Plus the root’s size $1$ only if a child has the same value as the root does, or $2$ if both children satisfy this constraint.
    • Time: $O(N)$
    • Space: $O(h)$

Check out the improved code below directly. Bad code is just a record of my first attempt.

Bad Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private int result = Integer.MIN_VALUE;

public int longestUnivaluePath(TreeNode root) {
if (root == null) {
return 0;
}
result = Integer.MIN_VALUE;
sameLength(root);
return result - 1; // #link = #node - 1
}

private int lengthHelper(TreeNode root) {
if (root == null) {
return 0;
}

// should go in depth first
int leftMax = sameLength(root.left);
int rightMax = sameLength(root.right);

// only on one side
int maxOnOneSide = 1; // the root + one max side. At least only the root satisfies
int maxOnBothSides = 1; // the root + both max sides

// left
if (root.left != null && root.left.val == root.val) {
maxOnOneSide += leftMax;
maxOnBothSides += leftMax;
}

// right
if (root.right != null && root.right.val == root.val) {
maxOnOneSide = Math.max(maxOnOneSide, 1 + rightMax);
maxOnBothSides += rightMax;
}

// update the global result
result = Math.max(maxLength, maxOnBothSides);

return maxOnOneSide;
}

Improved code:

  • I first compute the length as the number of nodes in the path and I will subtract one at the end.
  • Since this problem is a counting problem, we can initialize result as 0 instead of Integer.MIN_VALUE. However, in this case, we want the final result subtracted by one, so we can initialize it with 1.
    • root == null returns 0.
    • size == 1 return 0.
  • recursive call must be put in advance, since we need to go depth to update the global max property.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private int result;

public int longestUnivaluePath(TreeNode root) {
if (root == null) {
return 0;
}
result = 1;
lengthHelper(root);
return result - 1; // #link = #node - 1
}

private int lengthHelper(TreeNode root) {
if (root == null) {
return 0;
}

int L = lengthHelper(root.left);
int R = lengthHelper(root.right);

// We need to check if left and right are OKAY with the root.
// If not, keep it as 0. => clean code
int validLeft = 0, validRight = 0;

if (root.left != null && root.left.val == root.val) {
validLeft = L;
}

if (root.right != null && root.right.val == root.val) {
validRight = R;
}

// crossing the root / or not crossing the root
this.result = Math.max(this.result, validLeft + validRight + 1);

return 1 + Math.max(validLeft, validRight); // must includes root + one path (maybe 0)
}