力扣 二叉树 相关题目总结2

目录

[一、101 对称二叉树](#一、101 对称二叉树)

题目

题解

方法一:递归(推荐)

方法二:迭代

[二、100 相同的树](#二、100 相同的树)

题目

题解

方法一:递归法

方法二:深度优先搜索

[三、111 二叉树的最小深度](#三、111 二叉树的最小深度)

题目

题解

方法一:递归法

[四、226 翻转二叉树](#四、226 翻转二叉树)

题目

题解


一、101 对称二叉树

题目

给你一个二叉树的根节点 root , 检查它是否轴对称。

复制代码
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true

示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
 
提示:
树中节点数目在范围 [1, 1000] 内
-100 <= Node.val <= 100

题解

方法一:递归(推荐)

乍一看无从下手,但用递归其实很好解决。

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。

根据题目的描述,镜像对称,就是左右两边相等,也就是左子树和右子树是相当的。注意这句话,左子树和右子相等,也就是说要递归的比较左子树和右子树。

我们将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。如果相当,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点。

比如看下面这两个子树(他们分别是根节点的左子树和右子树),能观察到这么一个规律:

  • 左子树的左孩子 == 右子树的右孩子
  • 左子树的右孩子 == 右子树的左孩子

(1)确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)

  • 左节点为空,右节点不为空,不对称,return false
  • 左不为空,右为空,不对称 return false
  • 左右都为空,对称,返回true

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:左右都不为空,比较节点数值,不相同就return false。

此时左右节点不为空,且数值也不相同的情况我们也处理了。根据上面信息可以总结出递归函数的两个终止条件:

  1. left 和 right 不等,或者 left 和 right 都为空
  2. 递归的比较 left.left 和 right.right,递归比较left.right 和 right.left

(2)确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回true ,有一侧不对称就返回false 。

(3)代码如下,

java 复制代码
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;  // 如果根节点为null,即空树,视为对称二叉树,返回true
        }
        return isMirror(root.left, root.right);  // 调用isMirror方法判断左子树和右子树是否对称
    }

    private boolean isMirror(TreeNode left, TreeNode right) {
        if (left == null && right == null) {
            return true;  // 如果左子树和右子树都为null,也视为对称,返回true
        }
        if (left == null || right == null) {
            return false;  // 如果左子树和右子树只有一个为null,视为不对称,返回false
        }
        return left.val == right.val && isMirror(left.left, right.right) && isMirror(left.right, right.left);
        // 如果左子树和右子树的值相等,且同时满足左子树的左子树和右子树的右子树对称,
        // 以及左子树的右子树和右子树的左子树对称,则视为对称,返回true;否则,返回false
    }
}

isSymmetric方法是该函数的入口,接收一个二叉树的根节点作为参数。

  • 首先判断根节点是否为null,如果是,即空树,视为对称二叉树,返回true。否则,调用isMirror 方法来判断左子树和右子树是否对称。

isMirror方法是递归判断左右子树是否对称的函数。

  • 首先判断左子树和右子树是否都为null,如果是,即均为空树,视为对称,返回true。
  • 然后判断左子树和右子树中只有一个为null的情况,即一个为空树一个不为空树,视为不对称,返回false。
  • 最后,判断左子树的值和右子树的值是否相等,并且同时递归判断左子树的左子树和右子树的右子树是否对称,以及递归判断左子树的右子树和右子树的左子树是否对称。只有全部满足才视为对称,返回true;否则,返回false。

时间复杂度是O(n),因为我们遍历整个输入树一次,所以总的运行时间为 O(n),其中 n 是树中结点的总数。

空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n)。因此,在最糟糕的情况下,由栈上的递归调用造成的空间复杂度为 O(n)。

方法二:迭代

这道题目我们也可以使用迭代法,使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历)。

回想下递归的实现:当两个子树的根节点相等时,就比较:左子树的 left 和 右子树的 right,这个比较是用递归实现的。

现在我们改用队列来实现,把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。思路如下:

  • 首先从队列中拿出两个节点(left 和 right)比较:
  • 将 left 的 left 节点和 right 的 right 节点放入队列
  • 将 left 的 right 节点和 right 的 left 节点放入队列

代码如下,

java 复制代码
public static boolean isSymmetric(TreeNode root){
    Deque<TreeNode> deque = new LinkedList<>();
    deque.offerFirst(root.left);
    deque.offerLast(root.right);
    while (!deque.isEmpty()) {
        TreeNode leftNode = deque.pollFirst();
        TreeNode rightNode = deque.pollLast();
        if (leftNode == null && rightNode == null) {
            continue;
        }
        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }
        deque.offerFirst(leftNode.left);
        deque.offerFirst(leftNode.right);
        deque.offerLast(rightNode.right);
        deque.offerLast(rightNode.left);
    }
    return true;
}

使用双端队列 Deque 来存储需要比较的节点。将根节点的左子节点和右子节点添加到队列中。

当队列不为空时,进入循环。从队列中取出两个节点 leftright 进行比较。注意,这里使用 poll 方法每次从队列中取出两个节点。

如果两个节点都为空,则继续下一次循环,因为两个空节点是对称的。如果一个节点为空或者两个节点的值不同,则树不对称,返回 false

如果两个节点相等且不为空,将 left 节点的左子节点和 right 节点的右子节点加入队列的前端。将 left 节点的右子节点和 right 节点的左子节点加入队列的末端。这种方式确保了每次都比较成对的子节点,以保持对称性。

二、100 相同的树

题目

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

复制代码
提示:
两棵树上的节点数目都在范围 [0, 100] 内
-104 <= Node.val <= 104

题解

题意讲的很清楚,就是判断两颗二叉树是否完全相同,其实就可以梳理成以下三点:

  • p 树的根节点和 q 树的根节点比较。
  • p 树的左子树和 q 树的左子树比较。
  • p 树的右子树和 q 树的右子树比较。

方法一:递归法

具体算法代码实现如下:

java 复制代码
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 皆空,直接返回
        if(p == null && q == null)  return true;
        // 如果其中一棵树为空,另一棵不为空,则一定不相同
        if((p == null && q != null) || (p != null && q == null)) return false;
        // 如果两棵树皆不为空,但是根节点的值不同,则一定不相同。
        if(p.val !=q.val) return false;

        //排除以上特殊情况,只需要都为true则说明两树完全相同。
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);    
    }
}

时间复杂度:O(min(n,m))。其中 m 和 n 分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。

空间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。

方法二:深度优先搜索

具体算法代码实现如下:

java 复制代码
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        //两数都为空,直接返回
        if (p == null && q == null) {
            return true;
        //存在一方为空,则不可能相同
        } else if (p == null || q == null) {
            return false;
        } else if (p.val != q.val) {
            return false;
        } else {
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
        }
    }
}

时间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。

空间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。

其实以上两种思路本质上都差不多的,唯独就是解题思路方向上细致不太一样。都是是运用递归思想,连枚举方式都是一样的,先枚举排除特殊性,然后再递归对比左右子数是否相等。

三、111 二叉树的最小深度

题目

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

java 复制代码
说明:叶子节点是指没有子节点的节点

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2

示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

题解

方法一:递归法

递归计算每个结点的最小深度

  • 当当前节点是空,直接返回
  • 当左子树是空,且右子树不为空,则返回 右子树的最小深度 + 1
  • 当右子树是空,且左子树不为空,则返回 左子树的最小深度 + 1
  • 当左右子树均不为空,则返回 左右子树的最小深度的最小值 + 1

时间复杂度 O(n),n 为二叉树的节点数,需要遍历二叉树的所有节点。

java 复制代码
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        int l = minDepth(root.left);
        int r = minDepth(root.right);
        if(root.left == null && root.right != null) return 1 + r;
        if(root.right == null && root.left != null) return 1 + l;
        return 1 + Math.min(l, r);
    }
}

四、226 翻转二叉树

题目

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

java 复制代码
提示:
树中节点数目范围在 [0, 100] 内
-100 <= Node.val <= 100

题解

翻转一棵二叉树意味着交换每个节点的左右子树。我们可以使用递归的方法,从根节点开始,对每个节点进行如下操作:

  • 交换当前节点的左右子树
  • 递归地翻转当前节点的左子树
  • 递归地翻转当前节点的右子树

递归的终止条件是当前节点为 null,即叶子节点。

代码如下,

java 复制代码
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null) return root;

        TreeNode temp = root.left;
        root.left = root.right;
        root.right= temp;

        invertTree(root.left);
        invertTree(root.right);
        return root;
    }
}

递归函数需要访问每个节点恰好一次,因此时间复杂度为 O(n),其中 n 是树中节点的数量。

递归函数在递归过程中使用的栈空间的最大深度等于树的高度,最坏情况下,树是一个链表结构,高度为 n,因此空间复杂度为 O(n)。

相关推荐
想跑步的小弱鸡2 小时前
Leetcode hot 100(day 3)
算法·leetcode·职场和发展
战族狼魂3 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
xyliiiiiL4 小时前
ZGC初步了解
java·jvm·算法
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
爱的叹息4 小时前
RedisTemplate 的 6 个可配置序列化器属性对比
算法·哈希算法
hycccccch5 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
独好紫罗兰5 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
每次的天空6 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
天天向上杰6 小时前
面基JavaEE银行金融业务逻辑层处理金融数据类型BigDecimal
java·bigdecimal
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法