题目39-1:输入一颗二叉树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(包含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。二叉树定义如下。
public class TreeNode{
public int value;
public TreeNode leftNode;
public TreeNode rightNode;
public TreeNode() {}
public TreeNode(int value) {
this.value = value;
}
}
分析:
下图中的二叉树的深度为4,因为它从根结点到叶结点最长的路径包含4个结点(从根结点1开始,经过结点2和结点5,最终到达叶结点7)。
①如果一棵树只有一个结点,它的深度为1。
②如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。
③如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。
代码如下所示:
public static int treeDepth(TreeNode pNode) {
if (pNode == null) return 0;
int nLeft = treeDepth(pNode.leftNode);
int nRight = treeDepth(pNode.rightNode);
return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}
public static void main(String[] args) {
TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
TreeNode node7 = new TreeNode(7);
// 左右均有结点
// node1.leftNode = node2;
// node1.rightNode = node3;
// node2.leftNode = node4;
// node2.rightNode = node5;
// node3.rightNode = node6;
// node5.leftNode = node7;
// System.out.println(treeDepth(node1));
// 只有左结点
// node1.leftNode = node2;
// node2.leftNode = node3;
// node3.leftNode = node4;
// node4.leftNode = node5;
// System.out.println(treeDepth(node1));
// 只有右结点
// node1.rightNode = node2;
// node2.rightNode = node3;
// node3.rightNode = node4;
// node4.rightNode = node5;
// System.out.println(treeDepth(node1));
// 只有一个结点
System.out.println(treeDepth(node1));
// 结点为null
System.out.println(treeDepth(null));
}
题目39-2:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
分析:
在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一棵平衡的二叉树。
代码如下所示:
public static boolean isBalancedTree(TreeNode pNode) {
if (pNode == null) return true;
int left = treeDepth(pNode.leftNode);
int right = treeDepth(pNode.rightNode);
int dif = left - right;
if (dif > 1 || dif < -1) return false;
return isBalancedTree(pNode.leftNode) && isBalancedTree(pNode.rightNode);
}
注意到由于一个结点会被重复遍历多次,这种思路的时间效率不高。例如在isBalancedTree方法中输入上图中的二叉树,我们将首先判断根结点(结点1)是不是平衡的。此时我们往函数TreeDepth输入左子树的根结点(结点2)时,需要遍历结点4、5、7。接下来判断以结点2为根结点的子树是不是平衡树的时候,仍然会遍历结点4、5、7。毫无疑问,重复遍历同一个结点会影响性能。
换个角度来思考,如果我们用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们就已经遍历了它的左右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶节点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的。
代码如下所示:
static class Tuple {
private boolean isBalanced;
private int depth;
public Tuple() {
}
public Tuple(boolean isBalanced, int depth) {
super();
this.isBalanced = isBalanced;
this.depth = depth;
}
// 省略get、set方法
}
public static boolean isBalancedTree(TreeNode pNode) {
Tuple tuple = isBalanced(pNode);
return tuple.isBalanced();
}
public static Tuple isBalanced(TreeNode pNode) {
if (pNode == null) {
Tuple tuple = new Tuple();
tuple.setBalanced(true);
tuple.setDepth(0);
return tuple;
}
Tuple left = isBalanced(pNode.leftNode);
Tuple right = isBalanced(pNode.rightNode);
if (left.isBalanced() && right.isBalanced()) {
int dif = left.getDepth() - right.getDepth();
if (dif >= -1 && dif <= 1) {
return new Tuple(true, (left.getDepth() > right.depth ? left.getDepth() : right.getDepth()) + 1);
}
}
return new Tuple(false, -1);
}
public static void main(String[] args) {
TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
TreeNode node7 = new TreeNode(7);
// 平衡二叉树
// node1.leftNode = node2;
// node1.rightNode = node3;
// node2.leftNode = node4;
// node2.rightNode = node5;
// node3.rightNode = node6;
// node5.leftNode = node7;
// System.out.println(isBalancedTree(node1));
// 不是平衡的二叉树
// node1.leftNode = node2;
// node1.rightNode = node3;
// node2.leftNode = node4;
// node2.rightNode = node5;
// node5.rightNode = node6;
// node6.leftNode = node7;
// System.out.println(isBalancedTree(node1));
// 只有左结点
// node1.leftNode = node2;
// node2.leftNode = node3;
// node3.leftNode = node4;
// node4.leftNode = node5;
// System.out.println(isBalancedTree(node1));
// 只有右结点
// node1.rightNode = node2;
// node2.rightNode = node3;
// node3.rightNode = node4;
// node4.rightNode = node5;
// System.out.println(isBalancedTree(node1));
// 只有一个结点
System.out.println(isBalancedTree(node1));
// 结点为null
System.out.println(isBalancedTree(null));
}
题目40:一个整型数组里除了两个数字之外,其它的数字都出现了两次。请写出程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析:
这个问题可以可以使用用异或的性质解决。异或的性质:对于整数a,有:
(1)a^a=0
(2)a^0=a
(3)a^b^c=a^(b^c)=(a^c)^b
问题的思路如下:
(1)对于出现两次的元素,使用“异或”操作后结果肯定为0,那么我们就可以遍历一遍数组,对所有元素使用异或操作,那么得到的结果就是两个出现一次的元素的异或结果。
(2)因为这两个元素不相等,所以异或的结果肯定不是0,也就是可以在异或的结果中找到1位不为0的位的位置,记为第n位。
(3)这样就可以用第n位是不是1为标准,将原数组分成两个子数组。一组该位全为1,另一组该位全为0。出现两次的数字肯定会被分到同一个子数组中,且每个数组包含一个只出现一次的数字。
(4)分别对两个子数组求异或,就能找出只出现一次的数字。
public static int[] findNumsAppearOnce(int[] array) {
if (array == null || array.length < 2) return array;
int[] result = new int[2];
int resultExclusiveOR = array[0];
for (int i = 1; i < array.length; i++) {
resultExclusiveOR ^= array[i];
}
int bitIndex = 0;
for (int i = 0; i < 32; i++) { //找出异或结果为1的位。
if ((resultExclusiveOR >> i & 1) == 1) {
bitIndex = i;
break;
}
}
for (int i = 0; i < array.length; i++) { //根据bitIndex为1,将元素分为两组
if ((array[i] >> bitIndex & 1) == 1)
result[0] ^= array[i]; //对应位为1,亦或得到的结果
else
result[1] ^= array[i]; //对应位为0,亦或得到的结果
}
return result;
}
public static void main(String[] args) {
// int[] numberAppearOnce = findNumsAppearOnce(new int[]{1, 2, 3, 4, 2, 4, 5, 5});
// 不通过
int[] numberAppearOnce = findNumsAppearOnce(new int[]{1, 2, 3, 4, 5, 6, 7, 8});
// int[] numberAppearOnce = findNumsAppearOnce(new int[]{1});
// int[] numberAppearOnce = findNumsAppearOnce(new int[]{});
for (int i = 0; i < numberAppearOnce.length; i++) {
System.out.print(numberAppearOnce[i] + " ");
}
}
时间复杂度:O(n),空间复杂度:常数。
也可使用一个Map,Map对应的键值key就是数组中的元素,value就是这个元素出现的次数。这样我通过一次遍历数组中的元素,如果元素出现在map中,则将其对应的value加1,否则将元素添加到map中,这样遍历一遍数组,我们就可以得到数组中每个元素对应出现的次数,然后再通过遍历一遍map,返回value为1对应的key就是我们需要求得元素。