Day 15 二叉树part 03
222. 完全二叉树的节点个数
一般的二叉树记录节点个数的方法
java
private static int countNodes(TreeNode root) {
if (root == null) return 0;
int leftHeight = countNodes(root.left);
int rightHeight = countNodes(root.right);
int height = leftHeight + rightHeight + 1; //左子树的节点+右子树的节点 +1(本身的节点)
return height;
}
利用完全二叉树的特性
一个树(即使他不是满二叉树)其左右子树(持续向下遍历)出来比如是满二叉树,而满二叉树的节点数量为2^n -1
我们两种返回条件
- 遇到null返回0
- 遇到该子树为满二叉树就返回 该子数的节点数量
否则我们就继续递归左右子数,再求和返回节点数量
while循环,同一出发点,从左子树的左子树递归,从右子树的右子树递归,若两高度相等,就说明是满二叉树(两端不为null,中间的节点是不可能为null的,因为是完全二叉树,从左到右排列),我们这个时候就之间返回节点数量。
若不为满二叉树,我们就递归左、右子树,再相加+1返回即可
java
private static int countNodes(TreeNode root) {
if (root == null) return 0;
TreeNode left = root.left;
int leftHeight = 0;
TreeNode right = root.right;
int rightHeight = 0;
while (left != null) {
left = left.left;
leftHeight++;
}
while (right != null) {
right = right.right;
rightHeight++;
}
if (leftHeight == rightHeight) {
return (2 << leftHeight) - 1;
}
int leftNum = countNodes(root.left);
int rightNum = countNodes(root.right);
return leftNum + rightNum + 1; //返回当前节点
}
错误点:
java
TreeNode left = root;
TreeNode right = root;
以上是错误的,假设这棵子树的高度为3,我就干脆得到它的高度
但是我们后面计算子树节点的时候,是利用移位公式return (2 << leftHeight) - 1;
2向左移位1是4,移位0才是2, 我当时误以为2<<1还是本身,当作2^1来看了,我们求出的数据应该是要比深度小1才是
java
TreeNode left = root.left;
TreeNode right = root.right;
110. 平衡二叉树
求深度用先序 从表层逐个往下,遇到就加1
求高度用后序 因为要遍历完之后,加上一些值之后再返回
将左右子树的高度差返回,不符合就返回-1,左右子数高度返回回来,记得判断一下是否为-1,大问题拆解为小问题
注意此处是差值是绝对值,这样无论是左右哪个大都可以应对自如了
java
private static boolean isBalanced(TreeNode root) {
if (getHeight(root) != -1) return true;
return false;
}
private static int getHeight(TreeNode root) {
if (root == null) return 0;
int leftHeight = getHeight(root.left);
if (leftHeight == -1) return -1;//由于是循环调用,-1也作为一个标记,遇到直接返回
int rightHeight = getHeight(root.right);
if (rightHeight == -1) return -1;
// 左右子树高度相减绝对值左右子树高度差大于1,return -1表示已经不是平衡树了
if (Math.abs(leftHeight - rightHeight) > 1) {
return -1; //用-1去标记不平衡
}
return Math.max(leftHeight, rightHeight) + 1;//加1是加上本身的长度
}
错误
- 未递归检查所有子节点的平衡性
- 调用isBalanced时,重复调用isHeight函数,递归应当就写在一个函数当中,主函数就单纯调用函数即可,这样也暴露了函数的细节
java
private static boolean isBalanced(TreeNode root) {
if (root == null) return true;
int leftHeight = isHeight(root.left);
int rightHeight = isHeight(root.right);
if (Math.abs(leftHeight - rightHeight) > 1) return false;
return true;
}
private static int isHeight(TreeNode root) {
if (root == null) return 0;
int leftHeight = isHeight(root.left);
int rightHeight = isHeight(root.right);
return Math.max(leftHeight, rightHeight) + 1;
}
257. 二叉树的所有路径
递归内部其实都有回溯,只是将其隐藏了
中是处理的过程(路径收集),处理一定要放在判断是否为叶子节点之前,
StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快,由于线程不安全,建议不涉及线程的时候使用
java
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i)).append("->");
}
sb.append(paths.get(paths.size() - 1));// 记录最后一个节点,路径符号不在最后一位
这里我们,我们直接弹出最后一个元素,remove可以根据下标和值弹出
java
if (root.left != null) {
getPath(root.left, path, node);
node.remove(node.size() - 1);
}
一次递归调用中最多只进行一次收集,我们遇到叶子结点就正式将收集的元素拼接在一起,其余都是收集元素,收集的时候注意不要用错,我们节点的收集是node,我们一到叶子节点,我们就收集,集合起来是返回path。
java
private static List<String> binaryTreePaths(TreeNode root) {
if (root == null) return Collections.emptyList();
List<String> path = new ArrayList<>();
ArrayList<Integer> node = new ArrayList<>();
getPath(root, path, node);
return path;
}
private static void getPath(TreeNode root, List<String> path, ArrayList<Integer> node) {
node.add(root.val);//node而不是path
if (root.left == null && root.right == null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < node.size() - 1; i++) {
sb.append(node.get(i)).append("->");
}
sb.append(node.get(node.size() - 1));
path.add(sb.toString());
}
if (root.left != null) {
getPath(root.left, path, node);
node.remove(node.size() - 1);
}
if (root.right != null) {
getPath(root.right, path, node);
node.remove(node.size() - 1);
}
}
问题1:
因为这里我们的函数public void getPath(TreeNode root, List list, ArrayList node)已经将参数传递进来,我们就可以进行调用 ,这里其实和 c++一样,我们这里传入的是引用数据类型,背后是地址传输
404. 左叶子之和
整体概括:
我们就是要求左子树的左叶子和右子树的左叶子
我知道如何判断叶子节点,但是父节点的左孩子该如何判断呢? 我们遍历到这个节点,不知道这个节点是否是左叶子。我们遍历到父元素的时候,就先对它左孩子做判断:左孩子的左右子树是否为空,是叶子节点就是我们所要找的。
我们这里可以采用后序遍历,收集本节点的,返回当前这个节点的:左子树左子树的左叶子之和 以及 右子树的左叶子之和。后序遍历,下向上返回。
整体概括:
我们就是要求左子树的左叶子和右子树的左叶子。
(root.left!=null&&root.left.leftnull&&root.left.rightnull)这里的逻辑是,先判断左孩子是否为空,才有资格调用左孩子的左右孩子,不然会爆出空指针异常。 第一则判断是左节点不为空,而后两则是判断 这个是否为叶子节点。
问题:
我们不知道当前遍历到的节点是否为左孩子节点(虽然可以知道是否是子节点)。
java
private static int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
if (root.left == null && root.right == null) return 0;//就返回,但不知道是否是左孩子
int leftSum = sumOfLeftLeaves(root.left);
int rightSum = sumOfLeftLeaves(root.right);
int mid = 0;
//这个判断是什么意思 判断左子树不为空,并且判断是否为叶子节点
if (root.left != null && root.left.left == null && root.left.right == null) {
mid = root.left.val; //注意是root.left.val 而不是 root.val
}
return mid + leftSum + rightSum;
}