Medium

题目描述

给你二叉树的根节点 root 和一个整数 distance

如果二叉树中两个叶子节点之间的最短路径长度小于或者等于 distance ,那它们就可以构成一组好叶子节点对。

返回树中好叶子节点对的数量。

示例 1:

输入:root = [1,2,3,null,4], distance = 3
输出:1
解释:叶子节点是 3 和 4 ,它们之间的最短路径的长度是 3 。这是唯一的好叶子节点对。

示例 2:

输入:root = [1,2,3,4,5,6,7], distance = 3
输出:2
解释:好叶子节点对为 [4,5] 和 [6,7] ,最短路径长度都是 2 。但是叶子节点对 [4,6] 不满足要求,因为它们之间的最短路径长度为 4 。

示例 3:

输入:root = [7,1,4,6,null,5,3,null,null,null,null,null,2], distance = 3
输出:1
解释:唯一的好叶子节点对是 [2,5] 。

提示:

  • 树中节点数目在范围 [1, 2^10] 内。
  • 1 <= Node.val <= 100
  • 1 <= distance <= 10

解题思路

这是一道经典的二叉树深度优先搜索问题。主要思路是利用DFS后序遍历,对于每个节点,统计其子树中叶子节点到该节点的距离分布。

核心思想:

  1. 对于每个节点,我们需要知道其左右子树中叶子节点到该节点的距离分布
  2. 对于当前节点,好叶子节点对可能有两种情况:
    • 完全在左子树中(递归处理)
    • 完全在右子树中(递归处理)
    • 一个在左子树,一个在右子树(需要在当前节点计算)

算法步骤:

  1. 使用DFS后序遍历,对于每个节点返回该子树中叶子节点到当前节点的距离数组
  2. 对于叶子节点,返回距离为0的数组
  3. 对于内部节点,合并左右子树的距离数组,距离都加1
  4. 在合并过程中,检查左右子树的叶子节点是否能构成好叶子节点对
  5. 如果左子树中距离为i的叶子节点和右子树中距离为j的叶子节点满足i+j+2≤distance,则它们构成好叶子节点对

这种方法的优势是只需要一次DFS遍历,时间复杂度较优。

代码实现

class Solution {
public:
    int result = 0;
    
    vector<int> dfs(TreeNode* node, int distance) {
        if (!node) return {};
        
        // 如果是叶子节点,返回距离为0
        if (!node->left && !node->right) {
            return {1}; // 距离0处有1个叶子节点
        }
        
        vector<int> left = dfs(node->left, distance);
        vector<int> right = dfs(node->right, distance);
        
        // 计算跨越当前节点的好叶子节点对
        for (int i = 0; i < left.size(); i++) {
            for (int j = 0; j < right.size(); j++) {
                if (i + j + 2 <= distance) {
                    result += left[i] * right[j];
                }
            }
        }
        
        // 返回当前节点的距离分布(距离都加1)
        vector<int> curr(distance, 0);
        for (int i = 0; i < left.size() && i + 1 < distance; i++) {
            curr[i + 1] += left[i];
        }
        for (int i = 0; i < right.size() && i + 1 < distance; i++) {
            curr[i + 1] += right[i];
        }
        
        return curr;
    }
    
    int countPairs(TreeNode* root, int distance) {
        dfs(root, distance);
        return result;
    }
};
class Solution:
    def countPairs(self, root: Optional[TreeNode], distance: int) -> int:
        self.result = 0
        
        def dfs(node):
            if not node:
                return []
            
            # 如果是叶子节点,返回距离为0
            if not node.left and not node.right:
                return [1] + [0] * (distance - 1)
            
            left = dfs(node.left)
            right = dfs(node.right)
            
            # 计算跨越当前节点的好叶子节点对
            for i in range(distance):
                for j in range(distance):
                    if i + j + 2 <= distance:
                        self.result += left[i] * right[j]
            
            # 返回当前节点的距离分布(距离都加1)
            curr = [0] * distance
            for i in range(distance - 1):
                curr[i + 1] = left[i] + right[i]
            
            return curr
        
        dfs(root)
        return self.result
public class Solution {
    private int result = 0;
    
    private int[] Dfs(TreeNode node, int distance) {
        if (node == null) return new int[distance];
        
        // 如果是叶子节点,返回距离为0
        if (node.left == null && node.right == null) {
            int[] leaf = new int[distance];
            leaf[0] = 1;
            return leaf;
        }
        
        int[] left = Dfs(node.left, distance);
        int[] right = Dfs(node.right, distance);
        
        // 计算跨越当前节点的好叶子节点对
        for (int i = 0; i < distance; i++) {
            for (int j = 0; j < distance; j++) {
                if (i + j + 2 <= distance) {
                    result += left[i] * right[j];
                }
            }
        }
        
        // 返回当前节点的距离分布(距离都加1)
        int[] curr = new int[distance];
        for (int i = 0; i < distance - 1; i++) {
            curr[i + 1] = left[i] + right[i];
        }
        
        return curr;
    }
    
    public int CountPairs(TreeNode root, int distance) {
        result = 0;
        Dfs(root, distance);
        return result;
    }
}
var countPairs = function(root, distance) {
    let result = 0;
    
    function dfs(node) {
        if (!node) return new Array(distance).fill(0);
        
        // 如果是叶子节点,返回距离为0
        if (!node.left && !node.right) {
            const leaf = new Array(distance).fill(0);
            leaf[0] = 1;
            return leaf;
        }
        
        const left = dfs(node.left);
        const right = dfs(node.right);
        
        // 计算跨越当前节点的好叶子节点对
        for (let i = 0; i < distance; i++) {
            for (let j = 0; j < distance; j++) {
                if (i + j + 2 <= distance) {
                    result += left[i] * right[j];
                }
            }
        }
        
        // 返回当前节点的距离分布(距离都加1)
        const curr = new Array(distance).fill(0);
        for (let i = 0; i < distance - 1; i++) {
            curr[i + 1] = left[i] + right[i];
        }
        
        return curr;
    }
    
    dfs(root);
    return result;
};

复杂度分析

复杂度类型分析
时间复杂度O(N × distance²),其中 N 是树中节点的数量。对于每个节点,我们需要遍历距离数组进行计算,距离数组长度为 distance
空间复杂度O(H × distance),其中 H 是树的高度。递归调用栈的深度为 H,每次调用需要 O(distance) 的数组空间