1. 问题描述:
给你二叉树的根节点 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] 。
示例 4:
输入:root = [100], distance = 1
输出:0
示例 5:
输入:root = [1,1,1], distance = 2
输出:1
2. 思路分析:
① 我感觉这道关于二叉树的题目是使用递归方法解决的非常好的题目,非常值得学习怎么样去解决,题目一看很容易理解,但是真的动手去做的时候发现会有点困难,而且一开始的时候发现找到一个合适的思路还是比较难的,于是理解了一下力扣官方的题解,自己编写几个简单的测试用例开启debug模式进行调试,但是发现官方有的解释得比较晦涩难懂而且debug之后还是比较难理解,理解很久之后还是有用一点思路的,于是尝试找一下比较好理解的题解与代码再理解一下,在力扣的评论区发现一个思路与官网的基本上是一样的java代码,最终还是理解了,发现这个思路真的好棒,这个对于后面我们遇到相关的问题是很有帮助的,可以借鉴一下其中的思路来解决类似需求的问题,自己理解之后转为了python代码,下面是我自己的一些理解
② 我感觉主要有以下几个巧妙的点,第一个巧妙之处是使用一个数组count来记录当前节点到叶子节点距离为i的叶子节点的数目,count数组的长度为len(distance) + 1(数组的索引表示的是当前节点到叶子节点的距离),而且题目中规定了距离是不超过10的所以每一次递归之前开辟的空间都是较小的,我们每一层都声明这样一个数组,并且利用当前节点递归返回的左右子树的count数组:也就是左右子树中距离为i的叶子节点的数目来更新当前节点下的距离为i的叶子节点数目,这样就可以利用递归从下往上返回的时候计算当前节点下左右子树的在distance之内的组合情况了(感觉这个count数组的长度设置也是很合理的,因为题目规定了距离是在distance之内的,所以计算在distance之内的叶子节点的数目才是有效的,超过了这个distance就无效了)
感觉开辟的这个数组有点动态规划的味道,其实理解这个数组的含义对于理解代码还是很有帮助的
第二个巧妙之处是当递归完左右子树返回到当前根节点的时候,将当前根节点下的所有叶子节点的距离都实现了加1的操作,下面的代码很巧妙地实现了这一点(因为在递归调用完成之后退回到上一层的时候距离肯定是比下一层节点到叶子节点的距离是加1的):
将当前节点的左右子树到达叶子节点的数目累加到当前节点的count数组上,并且在累加的时候实现了当前节点到叶子节点的距离加1的操作(核心是 i - 1累加到i位置上这样就可以实现距离值加1),所以在层层返回的时候就实现了当前节点到叶子节点的距离和节点数目的更新,这样最后我们每一次层层返回到当前根节点的时候count数组才是正确的,这也恰恰验证了这个数组的含义了
第三个点是我们在返回到当前根节点的时候对左右子树进行组合(也就是将左右子树对应的叶子节点数目相乘),将当前节点的左右子树在distance之内的叶子的组合的数目累加到结果中,所以在递归的方法中需要返回一个参数就是当前节点下的count数组,记录当前节点到各个叶子节点的距离值,这样在层层返回的时候才可以计算当前节点下的满足条件的左右叶子节点的数目,假如一对叶子节点是好叶子节点那么它应该是左叶子节点与右节节点的距离值是在distance之内的,所以需要使用两层for循环来计算组合的情况,i, j分别表示的左叶子节点的距离与右叶子节点距离的组合(相乘表示左右叶子节点的组合情况),这里又涉及到边界的问题最简单的方法是写出具体的例子画出一棵简单的二叉树就可以确定了,比如i = 1,distance = 3,j的最大距离肯定是小于等于2的,也就是j = distance - i + 1
3. 代码如下:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
res = 0
def dfs(self, root: TreeNode, distance: int):
# count数组含义表示的是当前的根节点到叶子节点距离为i的叶子节点数目
count = [0] * (distance + 1)
if not root.left and not root.right:
# 距离为1的叶子节点的数目为1
count[1] = 1
return count
leftCount, rightCount = [0] * (distance + 1), [0] * (distance + 1)
if root.left:
leftCount = self.dfs(root.left, distance)
if root.right:
rightCount = self.dfs(root.right, distance)
# 对于边界问题最简单的是写出几个具体的例子来确定
for i in range(1, distance):
for j in range(1, distance - i + 1):
# 通过左右子树的叶子节点的数目相乘那么就可以表示当前节点下的左右子树的组合情况
self.res += leftCount[i] * rightCount[j]
# 在层层返回的时候使每个叶子节点到当前节点的距离加1
for i in range(2, distance + 1):
count[i] = leftCount[i - 1] + rightCount[i - 1]
return count
def countPairs(self, root: TreeNode, distance: int) -> int:
self.dfs(root, distance)
return self.res