题目(问题1):输入一棵二叉树的根节点,求该数的深度。从根节点到叶结点依次进过的结点(含根,叶结点)形成树的一条路径,最长路径的长度为树的深度。
例如,如下图的二叉树的深度为4,因为它从根节点到叶结点的最长的路径包含4个结点(从根结点1开始,经过2和结点5,最终到达叶结点7)
我们可以从另一种角度来理解树的深度。如果一棵树只有一个结点,它的深度为1,如果根节点只有左子树而没有右子树,那么树的深度应该是其左子树的深度+1.同样如果根节点只有右子树而没有左子树,那么树的深度应该是其右子树+1.如果既有左子树又有右子树,那概述的深度就是左、右子树的深度的较大值加1.。
如果公司对编程能力有较高的要求,面试官可能会追加一个与前面的问题相关但难度较大的问题,比如,应聘者做完上面的问题后,面试官追问:
题目二:输入一棵二叉树的根节点,判断该树是不是平衡的二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
方法1(看代码),每个节点遍历多次
需要重复遍历结点多次的解法,简单但不足以打动面试官
有了求二叉树的深度的经验之后再解决这个问题,我们很容易就能想到一个思路:在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差不超过1,按照定义它就是一棵平衡的二叉树。
方法2(看代码):每个节点遍历一次,后序遍历
但我们也注意到由于一个结点会被重复遍历多次,这种思路的时间效率不高。例如在函数isBalanced中输入上图中的二叉树,我们将首先判断根节点是不是平衡的。此时我们网函数TreeDepth输入左子树的根节点时需要遍历结点4,5,7.接下来判断以结点2为根节点的子树是不是平衡树的时候,仍然会遍历结点4,5,7.毫无疑问,重复遍历同一个结点会影响性能。接下来我们寻找不需要重复遍历的算法。
每个结点只遍历一次的解法,正是面试官喜欢的算法如果我们用后序遍历的方式遍历二叉树的每个结点,在遍历一个结点之前我们就已经遍历了它的左右子树。只要在遍历每个结点的时候我们记录它的深度(某一节点的深度等于它到叶结点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡二叉树。
代码:
public class Offer39 {
public static class BinaryTreeNode{
int value;
BinaryTreeNode left;
BinaryTreeNode right;
BinaryTreeNode(){}
BinaryTreeNode(int val){
this.value = val;
}
}
//问题1,求二叉树的深度
public int treeDepth(BinaryTreeNode root){
if(root == null)
return 0;
int nLeft = treeDepth(root.left);
int nRight = treeDepth(root.right);
return (nLeft>nRight) ? (nLeft+1):(nRight+1);
}
//问题2.输入一个二叉树的根节点,判断该树是不是平衡二叉树
//方法1,需要重复遍历节点多次的解法,不好
public boolean isBalanced(BinaryTreeNode root){
if(root == null)
return true;
int left = treeDepth(root.left);
int right = treeDepth(root.right);
int diff = left - right;
if(diff>1 || diff<-1)
return false;
return isBalanced(root.left)&&isBalanced(root.right);
}
//方法2,每个基点只遍历一次的解法,后序遍历二叉树
//高效率的判断是否是一棵平衡二叉树,照着课本上的c++代码翻译成java代码会出错,因为书中是&left,是引用传值,java中没有,这里用新建一个类解决这个问题
public static class reference{
private boolean isBalanced;
private int depth;
public reference(){}
public reference(boolean isBalanced, int depth){
super();
this.isBalanced = isBalanced;
this.depth = depth;
}
public boolean getIsBalanced() {
return isBalanced;
}
public void setBalanced(boolean isBalanced) {
this.isBalanced = isBalanced;
}
public int getDepth() {
return depth;
}
public void setDepth(int depth) {
this.depth = depth;
}
}
public reference isBalanced2(BinaryTreeNode root){
if(root == null){
reference re = new reference();
re.setBalanced(true);
re.setDepth(0);
return re;
}
reference left = isBalanced2(root.left);
reference right = isBalanced2(root.right);
if(left.getIsBalanced()&& right.getIsBalanced()){
int diff = left.getDepth() - right.getDepth();
if(diff<=1&& diff>=-1)
return new reference(true,(left.getDepth()>right.getDepth() ? left.getDepth() : right.getDepth())+1);
}
return new reference(false, -1);
}
public static void setBinaryTreeNode(BinaryTreeNode root,BinaryTreeNode left, BinaryTreeNode right){
if(root == null)
return;
root.left = left;
root.right = right;
}
public static void main(String[] args) {
Offer39 of39 = new Offer39();
/*
//功能测试,1,输入普通的二叉树
// 1
// / \
// 2 3
// /\ \
// 4 5 6
// /
// 7
BinaryTreeNode node11 = new BinaryTreeNode(1);
BinaryTreeNode node12 = new BinaryTreeNode(2);
BinaryTreeNode node13 = new BinaryTreeNode(3);
BinaryTreeNode node14 = new BinaryTreeNode(4);
BinaryTreeNode node15 = new BinaryTreeNode(5);
BinaryTreeNode node16 = new BinaryTreeNode(6);
BinaryTreeNode node17 = new BinaryTreeNode(7);
setBinaryTreeNode(node11, node12, node13);
setBinaryTreeNode(node12, node14, node15);
setBinaryTreeNode(node15, node17, null);
setBinaryTreeNode(node13, null, node16);
System.out.println(of39.treeDepth(node11));
//功能测试
// 02.二叉树的所有节点都没有左子树
// 1
// \
// 3
// \
// 6
BinaryTreeNode node21 = new BinaryTreeNode(1);
BinaryTreeNode node23 = new BinaryTreeNode(3);
BinaryTreeNode node26 = new BinaryTreeNode(6);
setBinaryTreeNode(node21, null, node23);
setBinaryTreeNode(node23, null, node26);
System.out.println(of39.treeDepth(node21));
//功能测试
// 03.二叉树的所有节点都没有右子树
// 1
// /
// 2
// /
// 4
BinaryTreeNode node31 = new BinaryTreeNode(1);
BinaryTreeNode node32 = new BinaryTreeNode(2);
BinaryTreeNode node34 = new BinaryTreeNode(4);
setBinaryTreeNode(node31, node32, null);
setBinaryTreeNode(node32, node34, null);
System.out.println(of39.treeDepth(node31));
//特殊输入测试
// 04.只有一个节点的二叉树
// 1
BinaryTreeNode node41 = new BinaryTreeNode(1);
setBinaryTreeNode(node41, null, null);
System.out.println(of39.treeDepth(node41));
//特殊输入测试,5
//指向二叉树的根节点的指针为NULL
BinaryTreeNode node51 = null;
System.out.println(of39.treeDepth(node51));
*/
//功能测试,1,平衡的二叉树
// 1
// / \
// 2 3
// /\ \
// 4 5 6
// /
// 7
BinaryTreeNode node11 = new BinaryTreeNode(1);
BinaryTreeNode node12 = new BinaryTreeNode(2);
BinaryTreeNode node13 = new BinaryTreeNode(3);
BinaryTreeNode node14 = new BinaryTreeNode(4);
BinaryTreeNode node15 = new BinaryTreeNode(5);
BinaryTreeNode node16 = new BinaryTreeNode(6);
BinaryTreeNode node17 = new BinaryTreeNode(7);
setBinaryTreeNode(node11, node12, node13);
setBinaryTreeNode(node12, node14, node15);
setBinaryTreeNode(node15, node17, null);
setBinaryTreeNode(node13, null, node16);
//System.out.println(of39.isBalanced(node11));
System.out.println(of39.isBalanced2(node11).getIsBalanced());
//功能测试,2,不平衡的二叉树
// 1
// / \
// 2 3
// /\ \
// 4 5 6
// /
// 7
// /
// 8
BinaryTreeNode node21 = new BinaryTreeNode(1);
BinaryTreeNode node22 = new BinaryTreeNode(2);
BinaryTreeNode node23 = new BinaryTreeNode(3);
BinaryTreeNode node24 = new BinaryTreeNode(4);
BinaryTreeNode node25 = new BinaryTreeNode(5);
BinaryTreeNode node26 = new BinaryTreeNode(6);
BinaryTreeNode node27 = new BinaryTreeNode(7);
BinaryTreeNode node28 = new BinaryTreeNode(8);
setBinaryTreeNode(node21, node22, node23);
setBinaryTreeNode(node22, node24, node25);
setBinaryTreeNode(node25, node27, null);
setBinaryTreeNode(node27, node28, null);
setBinaryTreeNode(node23, null, node26);
//System.out.println(of39.isBalanced(node21));
System.out.println(of39.isBalanced2(node21).getIsBalanced());
//功能测试
// 03.二叉树的所有节点都没有右子树
// 1
// /
// 2
// /
// 4
BinaryTreeNode node31 = new BinaryTreeNode(1);
BinaryTreeNode node32 = new BinaryTreeNode(2);
BinaryTreeNode node34 = new BinaryTreeNode(4);
setBinaryTreeNode(node31, node32, null);
setBinaryTreeNode(node32, node34, null);
//System.out.println(of39.isBalanced(node31));
System.out.println(of39.isBalanced2(node31).getIsBalanced());
//功能测试
// 04.二叉树的所有节点都没有左子树
// 1
// \
// 3
// \
// 6
BinaryTreeNode node41 = new BinaryTreeNode(1);
BinaryTreeNode node43 = new BinaryTreeNode(3);
BinaryTreeNode node46 = new BinaryTreeNode(6);
setBinaryTreeNode(node41, null, node43);
setBinaryTreeNode(node43, null, node46);
//System.out.println(of39.isBalanced(node41));
System.out.println(of39.isBalanced2(node41).getIsBalanced());
//特殊输入测试
// 05.只有一个节点的二叉树
// 1
BinaryTreeNode node51 = new BinaryTreeNode(1);
setBinaryTreeNode(node51, null, null);
//System.out.println(of39.isBalanced(node51));
System.out.println(of39.isBalanced2(node51).getIsBalanced());
//特殊输入测试,6
//指向二叉树的根节点的指针为NULL
BinaryTreeNode node61 = null;
//System.out.println(of39.isBalanced(node61));
System.out.println(of39.isBalanced2(node61).getIsBalanced());
}
}
运行结果(问题1:二叉树的深度):
4
3
3
1
0
运行结果(问题2:判断平衡二叉树)
true
false
false
false
true
true
为什么新建一个reference类?
在上面的问题2的第二种解法代码中,我们使用后序遍历的方式遍历整颗二叉树。在遍历某结点的左右子结点之后,我们可以根据它的左右子结点的深度判断它是不是平衡的,并得到当前结点的深度。当遍历到根结点的时候,也就判断了整颗二叉树是不是平衡二叉树。由于要传递两个参数,一般的使用返回值的方法是行不通的,而且Java并不存在指针和简单数据类型的引用传值。一般的高级语言(如Python)会有元组这么一个概念(Java没有那就自己定义一个),既然只能返回一个值,那就返回一个复合类型的,函数改造完成~
我想说的是,每个入了门的程序员都知道参数是复制传值,在C/C++中只能用指针和引用的方式从参数列表中传递或获取值,在Java中,除了基本数据类型和String类型外,也是引用传值。但是基本数据类型传进函数体你改动了有什么意义?你只是改动了一个副本。