Number of Good Leaf Nodes Pairs solution and explanation
9 more videos on this problem
Number of Good Leaf Nodes Pairs - Leetcode 1530 - PythonNeetCodeIO · 20:22 · 14K views
Leetcode 1530. Number of Good Leaf Nodes PairsFraz · 12:48 · 12K views
Number of Good Leaf Nodes Pairs | Using Graph And BFS | Dry Run | Leetcode 1530 | codestorywithMIKcodestorywithMIK · 28:55 · 12K views
1530. Number of Good Leaf Nodes Pairs | LCA | BFS | DFS | 4 ApproachesAryan Mittal · 22:35 · 6K views
Number of Good Leaf Nodes Pairs | Simple DFS | Dry Run | Leetcode 1530 | codestorywithMIKcodestorywithMIK · 31:30 · 4K views
NUMBER OF GOOD LEAF NODE PAIRS | PYTHON | LEETCODE # 1530Cracking FAANG · 17:13 · 2K views
Leetcode 1530. Number of Good Leaf Nodes Pairs (Python)AH Tech · 9:33 · 1K views
1530. Number of Good Leaf Nodes Pairs (Leetcode Medium)Programming Live with Larry · 19:38 · 883 views
Number of Good Leaf Nodes Pairs (Leetcode 1530)Algorithms Casts · 35:12 · 280 views
LeetCode 1530 - Number of Good Leaf Nodes Pairs - JavaAlpha-Code · 21:11 · 105 views
The problem
You are given the root of a binary tree and an integer distance. A pair of two different leaf nodes of a binary tree is said to be good if the length of the shortest path between them is less than or equal to distance.
Return the number of good leaf node pairs in the tree.
Example 1:
Input: root = [1,2,3,null,4], distance = 3 Output: 1 Explanation: The leaf nodes of the tree are 3 and 4 and the length of the shortest path between them is 3. This is the only good pair.
Example 2:
Input: root = [1,2,3,4,5,6,7], distance = 3 Output: 2 Explanation: The good pairs are [4,5] and [6,7] with shortest path = 2. The pair [4,6] is not good because the length of ther shortest path between them is 4.
Example 3:
Input: root = [7,1,4,6,null,5,3,null,null,null,null,null,2], distance = 3 Output: 1 Explanation: The only good pair is [2,5].
Constraints:
- The number of nodes in the
treeis in the range[1, 210]. 1 <= Node.val <= 1001 <= distance <= 10
Approach overview
Problem Overview: Given a binary tree and an integer distance, count pairs of leaf nodes whose shortest path length is less than or equal to that distance. The path length is measured by the number of edges between the two leaves.
Approach 1: DFS with Distance Array (O(n · d²) time, O(h · d) space)
Use a postorder depth-first search to aggregate distances from leaf nodes upward. Each recursive call returns an array where index i represents how many leaf nodes exist at distance i from the current node. For leaf nodes, return an array with distance 1. When processing an internal node, combine the left and right arrays: iterate through all pairs of distances i and j and count pairs where i + j ≤ distance. Then build a new array for the parent by shifting distances by one edge. The key insight is that every valid pair must have its lowest common ancestor at some node visited during DFS, so counting during the merge step captures all valid pairs efficiently.
This approach keeps the distance arrays capped at the maximum allowed distance, which keeps the merge step small. Since distance ≤ 10 in constraints, the nested loop over distances is effectively constant. The algorithm visits each node once and performs small merges, giving near-linear performance for practical inputs.
Approach 2: Preprocessing with Dynamic Programming (O(n · d²) time, O(n · d) space)
Another strategy treats each node as a subproblem and stores the number of leaves at each distance within its subtree. Using tree-based dynamic programming, compute a DP table where dp[node][k] represents how many leaf nodes exist at distance k from that node. Build this table bottom-up during a traversal of the tree. When combining children, compute cross-subtree leaf pairs by checking all i from the left subtree and j from the right subtree where i + j ≤ distance. Then shift distances by one to populate the current node's DP row.
This method makes the state explicit and can be easier to reason about if you think in terms of subtree summaries. The tradeoff is higher memory usage since each node keeps its own distance table instead of returning temporary arrays.
Recommended for interviews: DFS with distance arrays. It demonstrates strong understanding of tree traversal, subtree aggregation, and lowest common ancestor reasoning. Interviewers expect candidates to recognize that valid leaf pairs must meet at an internal node and can be counted during a postorder merge. Explaining the brute force idea (checking all leaf pairs with LCA or BFS) shows baseline understanding, but the DFS aggregation approach shows stronger algorithmic design.
Approach 1: DFS with Distance Array
This approach involves a DFS traversal of the tree. For each node, we compute the number of leaf nodes at each possible distance. We merge the results from the left and right subtrees and count the valid leaf pairs.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def countPairs(self, root: TreeNode, distance: int) -> int:
def dfs(node):
if not node:
return [0] * (distance + 1)
if not node.left and not node.right:
leaves = [0] * (distance + 1)
leaves[1] = 1
return leaves
left = dfs(node.left)
right = dfs(node.right)
for i in range(1, distance + 1):
for j in range(1, distance - i + 1):
self.result += left[i] * right[j]
leaves = [0] * (distance + 1)
for i in range(distance):
leaves[i + 1] = left[i] + right[i]
return leaves
self.result = 0
dfs(root)
return self.resultclass TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public int countPairs(TreeNode root, int distance) {
int[] result = new int[1];
dfs(root, distance, result);
return result[0];
}
private int[] dfs(TreeNode node, int distance, int[] result) {
if (node == null) return new int[distance + 1];
if (node.left == null && node.right == null) {
int[] leaves = new int[distance + 1];
leaves[1] = 1;
return leaves;
}
int[] left = dfs(node.left, distance, result);
int[] right = dfs(node.right, distance, result);
for (int i = 1; i <= distance; i++) {
for (int j = 1; j + i <= distance; j++) {
result[0] += left[i] * right[j];
}
}
int[] leaves = new int[distance + 1];
for (int i = 1; i < distance; i++) {
leaves[i + 1] = left[i] + right[i];
}
return leaves;
}
}#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
int countPairs(TreeNode* root, int distance) {
int result = 0;
dfs(root, distance, result);
return result;
}
private:
vector<int> dfs(TreeNode* node, int distance, int &result) {
if (!node) return vector<int>(distance + 1, 0);
if (!node->left && !node->right) {
vector<int> leaves(distance + 1, 0);
leaves[1] = 1;
return leaves;
}
vector<int> left = dfs(node->left, distance, result);
vector<int> right = dfs(node->right, distance, result);
for (int i = 0; i <= distance; ++i) {
for (int j = 0; j + i <= distance; ++j) {
result += left[i] * right[j];
}
}
vector<int> leaves(distance + 1, 0);
for (int i = 1; i < distance; ++i) {
leaves[i + 1] = left[i] + right[i];
}
return leaves;
}
};function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
var countPairs = function(root, distance) {
let result = 0;
function dfs(node) {
if (!node) return Array(distance + 1).fill(0);
if (!node.left && !node.right) {
let leaves = Array(distance + 1).fill(0);
leaves[1] = 1;
return leaves;
}
let left = dfs(node.left);
let right = dfs(node.right);
for (let i = 1; i <= distance; i++) {
for (let j = 1; j <= distance - i; j++) {
result += left[i] * right[j];
}
}
let leaves = Array(distance + 1).fill(0);
for (let i = 1; i < distance; i++) {
leaves[i + 1] = left[i] + right[i];
}
return leaves;
}
dfs(root);
return result;
};#include <stdio.h>
#include <stdlib.h>
#define MAX_DIST 10
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
int distanceArray[MAX_DIST + 1];
int dfs(TreeNode* node, int distance, int* result) {
if (!node) return NULL;
int leftDistances[MAX_DIST + 1] = {0};
int rightDistances[MAX_DIST + 1] = {0};
if (!node->left && !node->right) {
distanceArray[0] = 1;
return 1;
}
if (node->left) {
dfs(node->left, distance, result);
for (int i = 0; i < MAX_DIST; ++i) {
leftDistances[i + 1] = distanceArray[i];
}
}
if (node->right) {
dfs(node->right, distance, result);
for (int i = 0; i < MAX_DIST; ++i) {
rightDistances[i + 1] = distanceArray[i];
}
}
for (int i = 0; i <= distance; ++i) {
for (int j = 0; j + i <= distance; ++j) {
*result += leftDistances[i] * rightDistances[j];
}
}
for (int i = 0; i <= distance; ++i) {
distanceArray[i] = leftDistances[i] + rightDistances[i];
}
return 1;
}
int countPairs(TreeNode* root, int distance) {
int result = 0;
dfs(root, distance, &result);
return result;
}public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) { val = x; }
}
public class Solution {
public int CountPairs(TreeNode root, int distance) {
int result = 0;
Dfs(root, distance, ref result);
return result;
}
private int[] Dfs(TreeNode node, int distance, ref int result) {
if (node == null) return new int[distance + 1];
if (node.left == null && node.right == null) {
int[] leaves = new int[distance + 1];
leaves[1] = 1;
return leaves;
}
int[] left = Dfs(node.left, distance, ref result);
int[] right = Dfs(node.right, distance, ref result);
for (int i = 1; i <= distance; i++) {
for (int j = 1; j <= distance - i; j++) {
result += left[i] * right[j];
}
}
int[] leaves = new int[distance + 1];
for (int i = 1; i < distance; i++) {
leaves[i + 1] = left[i] + right[i];
}
return leaves;
}
}Python leverages a similar approach to handle distances with lists. Recursion helps traverse from leaves upwards, combining results facilitated by helper lists for each node.
Time Complexity: O(n * distance)
Space Complexity: O(distance)
Approach 2: Preprocessing with Dynamic Programming
This approach leverages preprocessing leaf distances in a dynamic programming table, which gives quick lookups during pair evaluation. It builds the DP table during a DFS traversal, allowing efficient pair computation afterward.
# Python strategy could involve creating structures like dicts or lists to preprocess dynamic pair info, then utilize partial evaluations.
class Solution:
def countPairs(self, root: TreeNode, distance: int) -> int:
# Setup for initial calculations, commence DP approach with Python lists.
# Completed implementation would expand and finalize timing mechanisms.class Solution {
public int countPairs(TreeNode root, int distance) {
// Preprocess distances using a List or Map and engage in dynamic programming theory.
// Outline is provided for further completion.
}
}/* Like C, a C++ solution leverages advanced structures to track pair-wise leaf distance calculations efficiently within tree traversal. A rough sample plan provides guidance for completing a full solution. *//* This solution is based on C and would implement a similar strategy to preprocess and calculate distances between all leaves, followed by evaluating valid pairs. For brevity, a sample plan is provided without full implementation due to competition space. */Python's dynamic and highly flexible types enable versatile data management in pursuit of this particular solution type, managing memory and computational efficiency.
Time Complexity: Ostensibly O(n^2) during in-depth pairwise solution analysis.
Space Complexity: Nearing O(n^2) from paired leaf storage events.
Approach 3: Recursion
The problem asks for the number of good leaf node pairs in a binary tree. The answer can be divided into three parts: the number of good leaf node pairs in the left subtree, the number of good leaf node pairs in the right subtree, and the number of good leaf node pairs formed by leaf nodes from the left subtree and leaf nodes from the right subtree.
We can solve this recursively.
The time complexity is O(n times d^2 times h), where n is the number of nodes in the binary tree, and h and d are the height of the binary tree and the distance limit, respectively. The space complexity is O(h) for the recursion stack.
function countPairs(root: TreeNode | null, distance: number): number {
const pairs: number[][] = [];
const dfs = (node: TreeNode | null): number[][] => {
if (!node) return [];
if (!node.left && !node.right) return [[node.val, 1]];
const left = dfs(node.left);
const right = dfs(node.right);
for (const [x, dx] of left) {
for (const [y, dy] of right) {
if (dx + dy <= distance) {
pairs.push([x, y]);
}
}
}
const res: number[][] = [];
for (const arr of [left, right]) {
for (const x of arr) {
if (++x[1] <= distance) res.push(x);
}
}
return res;
};
dfs(root);
return pairs.length;
}/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func countPairs(root *TreeNode, distance int) int {
if root == nil {
return 0
}
ans := countPairs(root.Left, distance) + countPairs(root.Right, distance)
cnt1 := make([]int, distance)
cnt2 := make([]int, distance)
dfs(root.Left, cnt1, 1)
dfs(root.Right, cnt2, 1)
for i, v1 := range cnt1 {
for j, v2 := range cnt2 {
if i+j <= distance {
ans += v1 * v2
}
}
}
return ans
}
func dfs(root *TreeNode, cnt []int, i int) {
if root == nil || i >= len(cnt) {
return
}
if root.Left == nil && root.Right == nil {
cnt[i]++
return
}
dfs(root.Left, cnt, i+1)
dfs(root.Right, cnt, i+1)
}Complexity comparison
| Approach | Complexity |
|---|---|
| DFS with Distance Array | Time Complexity: O(n * distance) |
| Preprocessing with Dynamic Programming | Time Complexity: Ostensibly O(n^2) during in-depth pairwise solution analysis. |
| Recursion | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| DFS with Distance Array | O(n · d²) | O(h · d) | Best general solution for interviews and coding platforms; efficient when the distance limit is small. |
| Preprocessing with Dynamic Programming | O(n · d²) | O(n · d) | Useful when you want explicit subtree DP states or need reusable distance data for additional queries. |
Frequently asked questions
Is Number of Good Leaf Nodes Pairs easy or hard?
Number of Good Leaf Nodes Pairs is classified as Medium difficulty. The challenge is recognizing that leaf pairs can be counted while merging subtree distance information during DFS rather than comparing every leaf pair directly.
Number of Good Leaf Nodes Pairs Python/Java solution
Python and Java implementations typically use a recursive DFS that returns an integer array representing leaf counts by distance. During the merge step, nested loops combine distances from left and right children to count valid leaf pairs.
How to solve Number of Good Leaf Nodes Pairs in O(n)?
A near-linear solution uses a single DFS traversal that aggregates leaf distances from subtrees. Each node returns an array of counts for leaves at different depths. While the theoretical complexity is O(n · d²), the small bound on d keeps operations constant per node, making the runtime close to O(n).
What is the best approach for Number of Good Leaf Nodes Pairs?
The most common approach uses DFS with distance arrays. During a postorder traversal, each node returns counts of leaf nodes at different distances in its subtree. When merging left and right children, pairs whose combined distance is ≤ the given limit are counted. This runs in O(n · d²) time where d is the distance limit.
Is Number of Good Leaf Nodes Pairs asked at Google/Amazon/Meta?
Tree traversal and leaf distance aggregation problems frequently appear in interviews at companies like Google, Amazon, and Meta. Variants of this problem test depth-first search, lowest common ancestor reasoning, and dynamic programming on trees.
What data structure is used in Number of Good Leaf Nodes Pairs?
The problem primarily uses a binary tree with depth-first search traversal. During recursion, arrays or small frequency lists track how many leaf nodes appear at each distance from the current node.
What is the time complexity of Number of Good Leaf Nodes Pairs?
The optimal DFS solution runs in O(n · d²) time, where n is the number of nodes and d is the maximum allowed distance between leaves. Since d is typically ≤ 10 in the constraints, the nested distance checks are small constants, making the algorithm effectively linear in practice.
