代码随想录算法训练营_day15

题目信息 110. 平衡二叉树

  • 题目链接: https://leetcode.cn/problems/balanced-binary-tree/
  • 题目描述:
    给定一个二叉树,判断它是否是平衡二叉树

解法一: {{递归法}}

解题思路

这里强调一波概念:

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:

110.平衡二叉树2

关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。

因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)

有的同学一定疑惑,为什么104.二叉树的最大深度 (opens new window)中求的是二叉树的最大深度,也用的是后序遍历。

那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。

递归

此时大家应该明白了既然要求比较高度,必然是要后序遍历。

递归三步曲分析:

  1. 明确递归函数的参数和返回值

参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。

那么如何标记左右子树是否差值大于1呢?

如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。

所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。

代码如下:

// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
 int getHeight(TreeNode root)
  1. 明确终止条件

递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0

代码如下:

if (node == null) {
    return 0;
}
  1. 明确单层递归的逻辑

如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。

分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

代码实现

/**  
 * 递归法  
 */  
public boolean isBalanced(TreeNode root) {  
    return getHeight(root) != -1;  
}  
  
private int getHeight(TreeNode root) {  
    if (root == null) {  
        return 0;  
    }  
    int leftHeight = getHeight(root.left);  
    if (leftHeight == -1) {  
        return -1;  
    }  
    int rightHeight = getHeight(root.right);  
    if (rightHeight == -1) {  
        return -1;  
    }  
    // 左右子树高度差大于1,return -1表示已经不是平衡树了  
    if (Math.abs(leftHeight - rightHeight) > 1) {  
        return -1;  
    }  
    return Math.max(leftHeight, rightHeight) + 1;  
}

题目信息 257. 二叉树的所有路径

  • 题目链接: https://leetcode.cn/problems/binary-tree-paths/
  • 题目描述:
    给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

解法一: {{递归法}}

解题思路

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。

在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。

前序遍历以及回溯的过程如图:

257.二叉树的所有路径

我们先使用递归的方式,来做前序遍历。要知道递归和回溯就是一家的,本题也需要回溯。

#递归

  1. 递归函数参数以及返回值

要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下:

void traversal(TreeNode root, List<Integer> paths, List<String> res)

1

  1. 确定递归终止条件

在写递归的时候都习惯了这么写:

if (root == null) {
    终止处理逻辑
}

但是本题的终止条件这样写会很麻烦,因为本题要找到叶子节点,就开始结束的处理逻辑了(把路径放进result里)。

那么什么时候算是找到了叶子节点? 是当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。

所以本题的终止条件是:

if (root.left == null && root.right == null) {
    终止处理逻辑
}

为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。

再来看一下终止处理的逻辑。

这里使用vector 结构path来记录路径,所以要把vector 结构的path转为string格式,再把这个string 放进 result里。

那么为什么使用了vector 结构来记录路径呢? 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。

可能有的同学问了,我看有些人的代码也没有回溯啊。

其实是有回溯的,只不过隐藏在函数调用时的参数赋值里

代码实现

/**  
 * 递归法  
 */  
public List<String> binaryTreePaths(TreeNode root) {  
    List<String> res = new ArrayList<>();// 存最终的结果  
    if (root == null) {  
        return res;  
    }  
    List<Integer> paths = new ArrayList<>();// 作为结果中的路径  
    traversal(root, paths, res);  
    return res;  
}  
  
private void traversal(TreeNode root, List<Integer> paths, List<String> res) {  
    paths.add(root.val);// 前序遍历,中  
    // 遇到叶子结点  
    if (root.left == null && root.right == null) {  
        // 输出  
        StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快  
        for (int i = 0; i < paths.size() - 1; i++) {  
            sb.append(paths.get(i)).append("->");  
        }  
        sb.append(paths.get(paths.size() - 1));// 记录最后一个节点  
        res.add(sb.toString());// 收集一个路径  
        return;  
    }  
    // 递归和回溯是同时进行,所以要放在同一个花括号里  
    if (root.left != null) { // 左  
        traversal(root.left, paths, res);  
        paths.remove(paths.size() - 1);// 回溯  
    }  
    if (root.right != null) { // 右  
        traversal(root.right, paths, res);  
        paths.remove(paths.size() - 1);// 回溯  
    }  
}

题目信息 404. 左叶子之和

  • 题目链接: https://leetcode.cn/problems/sum-of-left-leaves/description/
  • 题目描述: 给定二叉树的根节点 root ,返回所有左叶子之和。

解法一: {{解法名称}}

解题思路

首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。

因为题目中其实没有说清楚左叶子究竟是什么节点,那么我来给出左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点

大家思考一下如下图中二叉树,左叶子之和究竟是多少?

404.左叶子之和 其实是0,因为这棵树根本没有左叶子!

但看这个图的左叶子之和是多少?

图二

相信通过这两个图,大家对最左叶子的定义有明确理解了。

那么判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。

如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:

if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
    左叶子节点处理逻辑
}

#递归法

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。

递归三部曲:

  1. 确定递归函数的参数和返回值

判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int

使用题目中给出的函数就可以了。

  1. 确定终止条件

如果遍历到空节点,那么左叶子值一定是0

    if (root == null) return 0;  

注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:

if (root == null) return 0;
if (root.left == null && root.right== null) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
  1. 确定单层递归的逻辑

当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

代码如下:

    int leftValue = sumOfLeftLeaves(root.left);    // 左  
    int rightValue = sumOfLeftLeaves(root.right);  // 右  
  
    int midValue = 0;  
    if (root.left != null && root.left.left == null && root.left.right == null) {  
        midValue = root.left.val;  
    }  
    int sum = midValue + leftValue + rightValue;  // 中  
    return sum;  

整体递归代码如下:

代码实现

public int sumOfLeftLeaves(TreeNode root) {  
    if (root == null) return 0;  
    int leftValue = sumOfLeftLeaves(root.left);    // 左  
    int rightValue = sumOfLeftLeaves(root.right);  // 右  
  
    int midValue = 0;  
    if (root.left != null && root.left.left == null && root.left.right == null) {  
        midValue = root.left.val;  
    }  
    int sum = midValue + leftValue + rightValue;  // 中  
    return sum;  
}

题目信息 222. 完全二叉树的节点个数

  • 题目链接: https://leetcode.cn/problems/count-complete-tree-nodes/
  • 题目描述:
    给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点

解法一: {{完全二叉树}}

解题思路

完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。

对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。

对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。

完全二叉树(一)如图: 222.完全二叉树的节点个数

完全二叉树(二)如图: 222.完全二叉树的节点个数1

可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。

这里关键在于如何去判断一个左子树或者右子树是不是满二叉树呢?

在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:

在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:

那有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:

如果这么想,大家就是对 完全二叉树理解有误区了,以上这棵二叉树,它根本就不是一个完全二叉树

判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:

    if (root == null) return 0;  
// 开始根据左深度和右深度是否相同来判断该子树是不是满二叉树
    TreeNode left = root.left;  
    TreeNode right = root.right;  
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
    while (left != null) {  // 求左子树深度  
        left = left.left;  
        leftDepth++;  
    }  
    while (right != null) { // 求右子树深度  
        right = right.right;  
        rightDepth++;  
    }  
    if (leftDepth == rightDepth) {  
        return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0  
    }  

递归三部曲,第三部,单层递归的逻辑:(可以看出使用后序遍历)

int leftDepth = countNodes(root.left);       // 左
int rightDepth = countNodes(root.right);     // 右
int result = leftDepth + rightDepth + 1;    // 中
return result;

该部分精简之后代码为:

    return countNodes(root.left) + countNodes(root.right) + 1;  

最后整体代码如下:

代码实现

/**  
 * 针对完全二叉树的解法  
 *  
 * 满二叉树的结点数为:2^depth - 1  
 */public int countNodes(TreeNode root) {  
    if (root == null) return 0;  
    TreeNode left = root.left;  
    TreeNode right = root.right;  
    int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便  
    while (left != null) {  // 求左子树深度  
        left = left.left;  
        leftDepth++;  
    }  
    while (right != null) { // 求右子树深度  
        right = right.right;  
        rightDepth++;  
    }  
    if (leftDepth == rightDepth) {  
        return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0  
    }  
    return countNodes(root.left) + countNodes(root.right) + 1;  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值