力扣 二叉树 相关题目总结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 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展
@小博的博客10 分钟前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
沈询-阿里43 分钟前
java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl
java·开发语言
AaVictory.1 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
LuckyLay1 小时前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
向阳12181 小时前
Dubbo负载均衡
java·运维·负载均衡·dubbo
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Gu Gu Study2 小时前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
Ni-Guvara2 小时前
函数对象笔记
c++·算法