题目一:输入一棵二叉树的根结点,求该树的深度。从根结点到叶结点一次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
二叉树的结点定义如下:
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
在本题中面试官给出了一种树的深度的定义,我们可以根据这个定义去得到树的所有路径,也就能得到最长的路径及它的长度。我们可以尝试更加简洁的方法。
我们还可以从另外一个角度来理解树的深度。如果一棵树只有一个结点,它的深度为1.如果根结点只有左子树而没有右子树,那么树的深度应该是其右子树的深度加1.如果既有右子树而没有左子树,那么输的深度应该就是右子树的深度加1.如果既有左子树又有右子树,那该树的深度就是其左右子树深度较大的加1。
这个思路用递归很容易实现,只需对遍历的代码稍作修改即可:
int TreeDepth(BinaryTreeNode* pRoot)
{
if(pRoot == NULL)
return 0;
int nLeft = TreeDepth(pRoot->m_pLeft);
int nRight = TreeDepth(pRoot->m_pRight);
return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}
只要应聘者对二叉树这一数据结构很熟悉,就能很快写出上面的代码。如果公司对编程能力有较高要求,面试官可能会追加一个与前面问题相关难度更大的问题。比如,在应聘者做完上面的问题之后,面试官追问:
题目二:输入一颗二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一颗平衡二叉树。
需要重复遍历结点多次的解法,简单但不足以打动面试官
有了求二叉树的深度的经验之后再解决这个问题,我们就很容易能够想到一个思路:在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一颗平衡的二叉树。这种思路对应的代码如下:
bool IsBalanced(BinaryTreeNode* pRoot)
{
if(pRoot == NULL)
return true;
int left = TreeDepth(pRoot->m_pLeft);
int right = TreeDepth(pRoot->m_pRight);
int diff = left - right;
if(diff > 1 || diff < -1)
return false;
return IsBalanced(pRoot->m_pLeft) && IsBalanced(pRoot->m_pRight);
}
上面的代码固然简洁,但我们注意到由于一个结点会被重复遍历多次,这种思路的时间效率不高。接下来我们寻找不需要重复遍历的算法。
每个结点只遍历一次的解法,正是面试官喜欢的
如果我们用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们就已经遍历了它的右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶结点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的,
bool IsBalanced(BinaryTreeNode* pRoot, int* pDepth)
{
if(pRoot == NULL){
*pDepth = 0;
return true;
}
int left, right;
if(IsBalanced(pRoot->m_pLeft, &left)
&& IsBalanced(pRoot->m_pRight, &right)){
int diff = left - right;
if(diff <= 1 && diff >= -1){
*pDepth = 1 + (left > right) ? left : right;
return true
}
}
return false;
}
我们只需给上面的函数传入二叉树的根结点及一个表示结点深度的整型变量即可:
bool IsTreeBalanced(BinaryTreeNode* pRoot)
{
int depth = 0;
return IsBalanced(pRoot, &depth);
}
在上面的代码中,我们用后序遍历的方式遍历正科二叉树。在遍历某结点的左右子结点之后,我们可以根据它的左右子结点的深度判断它是不是平衡的,并得到当前结点的深度。当最后遍历到树的根结点的时候,也就判断了整棵二叉树是不是平衡二叉树。