对于判断二叉树是否平衡,需要计算每个节点左右子树的高度,再求差。
所以,关键是要找到每一个根节点,并且计算其子树的高度差。
方法1
所以就需要一个函数去计算其每个子树的高度。
参考二叉树的最大深度
对于第一个节点而言
其需要得到左子树与右子树的高度,之后再进行比较判断
则我们就需要提供两个去向(或者说计算高度的函数分别要计算根节点的左右子树的高度)。
同时,递归到下一个根节点,也同样要进行计算其两个子树的高度,并且递归到下一个节点,直到遇到NULL。
当然,如果一个根节点的两个子树都是NULL,那这个子树依旧是平衡二叉树,返回一个true.
总而言之,对于平衡二叉树的判断,我们需要一个函数负责计算每个树的最大深度,一个函数负责向下递归找到树的每个根节点并根据的得到的子树的高度差去判断是否满足平衡二叉树的条件。
int TreeHight(struct TreeNode* root)
{
if (root == NULL)
return 0;
int LeftHight = TreeHight(root->left);
int RightHight = TreeHight(root->right);
return fmax(LeftHight, RightHight) + 1;
}
bool isBalanced(struct TreeNode* root) {
if (root == NULL)
return true;
return abs(TreeHight(root->left) - TreeHight(root->right)) < 2 && isBalanced(root->left) && isBalanced(root->right);
}
但是,再看看这种思路写出来的代码,不觉得很累赘吗?
是的,这串代码执行的时间复杂度是O(n^2)。
是因为,我们每次递归得到一个根节点后,我们都需要计算它的子树的高度。注意,是重新计算它的高度。
计算节点的子树高度
当返回节点到这里时
又进入TreeHight(struct TreeNode* root)
函数,计算其高度。从20向下递归,节点7和15,又会计算一遍他们子树的高度。
当回到节点3时,
要得到其左右子树高度,就相当于拿到20子树的高度。。。。
就这样不断计算子树高度,而每次计算又相当于从0开始,造成重复计算节点高度。
这就会造成多次重复计算。
而递归找到每一个节点需要时间复杂度O(n)。
而每次计算高度又是要遍历每个子树节点又要O(n)。
而总结下来就是递归的时间复杂度*每次递归中的时间复杂度
其时间复杂度为O(N^2)。
但是,如果是这种代码,是拿不到offer的,还会引来HR的不可思议的眼神。
所以,我们需要将代码的时间复杂度将为O(n)。
方法2
对于减低时间复杂度,我们无法去削减遍历每一个节点的复杂度,只能有一个修改方向,那就是减少对于子树最大深度的重复计算。
也就是说,要做到,遍历完一个节点的同时要知道其高度,并且可以判断是否满足平衡二叉树的条件。
有一种思路就是能有一个方法可以储存每一个树的高度,也就是要有一个变量再节点遍历结束时,其值正好就是其高度。
可以考虑采用一个变量传址调用。并能够将高度返回给上一层,也就是说上一层可以直接得到子树高度,不必再重新计算。
对于判断是否满足条件要靠
abs(lefthight - righthight) < 2;
来进行判断,不满足返回false。
而对于一个树而言,对于这整个树而言,是否满足条件,其前提是,其根节点的左右子树是否都满足平衡二叉树的条件。这个思想就跟方法1中的
&& isBalanced(root->left) && isBalanced(root->right);
体现的是完全一样的。在每次根节点的递归结束后都判断这个根节点的子树是否满足条件,不满足就返回false。我们考虑不满足作为判断条件,如果满足的话就需要返回上一层,
这个水太深把握不住
其实也没必要,因为递归结束会自动返回,我们只需要其false或true即可。
而对于每个根节点的子树的高度对需要分两种去遍历。
Treehight(root->left, &lefthight)
Treehight(root->right, &righthight)
而对于每一个子树的计算,都是从0开始计算。所以传进去的高度要先初始化为0。
如果遍历到为NULL时,其高度肯定要设为0.为什么?因为它真的没有节点,所以子树的高度要设为0。
if (root == NULL)
{
*hight = 0;
return true;
}
将代码思路归纳之后也就是
bool Treehight(struct TreeNode* root, int* hight)
{
if (root == NULL)
{
*hight = 0;
return true;
}
int lefthight = 0;
if (Treehight(root->left, &lefthight) == false)
return false;
int righthight = 0;
if (Treehight(root->right, &righthight) == false)
return false;
*hight = fmax(lefthight, righthight) + 1;
return abs(lefthight - righthight) < 2;
}
bool isBalanced(struct TreeNode* root) {
int hight = 0;
return Treehight(root, &hight);
}
通过这样,不需要重复计算,时间复杂度为O(n).
方法3
根据题目要求,我们只需要判断是否满足平衡二叉树的条件。
而二叉树的判断条件只有一个,就是
abs(lefthight - righthight)
是否满足小于等于1。
对于整个判断条件而言都是判断是否左右子树都满足条件。
其实也是和方法2差不多的逻辑。我们可以采用左右的高度来体现。
if (abs(LeftHight - RightHight) > 1)
return -1;
这个就是条件不满足的体现。
直接返回-1.让其子树的高度直接为-1。代替了true,false。
int TreeHight(struct TreeNode* root)
{
if (root == NULL)
return 0;
int LeftHight = TreeHight(root->left);
int RightHight = TreeHight(root->right);
if (LeftHight == -1 || RightHight == -1)
return -1;
if (abs(LeftHight - RightHight) > 1)
return -1;
return fmax(LeftHight, RightHight) + 1;
}
bool isBalanced(struct TreeNode* root) {
return TreeHight(root) >= 0;
}
就像代码体现的一样。只有满足条件的高度才能继续去计算树的高度,否则就一直返回-1。
只要出现一次abs(LeftHight - RightHight) > 1
的情况。
if (LeftHight == -1 || RightHight == -1)
return -1;
这个判断条件就会直接控制,不断返回-1.
这个思路是,只需找出一个反例即可。
对于符合条件的返回的都是其树高度,都会是大于0的,并以此为判断条件。
总体而言,都是灵活利用了二叉树左右子树的性质,这个还是难把握的。
本人菜鸟一个,如有问题,烦请大佬指点,感谢。