Leetcode 刷题笔记(十四) —— 二叉树篇之二叉树的属性相关题目

本文详细探讨了二叉树的各种性质,包括对称性、深度、高度、子树、路径等,并提供了多种解题方法,如递归、迭代。通过实例分析了101.对称二叉树、100.相同的树、572.另一棵树的子树、104.二叉树的最大深度、111.二叉树的最小深度等经典问题,阐述了二叉树遍历和深度优先搜索的应用。此外,还介绍了完全二叉树的节点个数、平衡二叉树的判断以及路径总和的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章目录

一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
更新中 …


前言

在上边两篇文章熟练使用二叉树的递归遍历和层序遍历基础上,本文记录了关于二叉树的一些属性如:深度、高度、相同二叉树及子树、平衡二叉树、二叉树的路径,以及结点的属性的相关题目,在二叉树的所有路径中还简单提到了回溯
刷题路线来自 :代码随想录

题录

101. 对称二叉树

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

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

示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
题解:
方法一: 递归

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return dfs(root.left, root.right);
    }
    public boolean dfs(TreeNode p, TreeNode q) {
    	// 先序遍历
        if (p == null && q == null) return true;
        // 肯定不都为null,一个为null时
        if (p == null || q == null) return false;
        // 不相等时
        if (p.val != q.val) return false;
        // 向左向右递归,只要有一个 false 就不会继续向下递归了
        return dfs(p.left, q.right) && dfs(p.right, q.left);
    }
}

方法二:迭代
同时从左从右遍历,使用两个队列记录各自的遍历结点并比较
注意: 这里队列是使用的链表结构,因为使用数组结构时,当孩子结点为空让孩子结点入队会出现异常

class Solution {
    public boolean isSymmetric(TreeNode root) {
        Queue<TreeNode> queue1 = new LinkedList<>();
        Queue<TreeNode> queue2 = new LinkedList<>();
        queue1.offer(root.left);
        queue2.offer(root.right);
        while (!queue1.isEmpty() && !queue2.isEmpty()) {
        	// 弹出两个对称的结点
            TreeNode node1 = queue1.poll();
            TreeNode node2 = queue2.poll();
            // 层序遍历,两头走到头为空,需要继续向中间走
            if (node1 == null && node2 == null) continue;
            // 不相等
            if (node1 == null || node2 == null) return false;
            if (node1.val != node2.val) return false;
			// 两个队列分别让结点入栈
            queue1.offer(node1.left);
            queue1.offer(node1.right);
            queue2.offer(node2.right);
            queue2.offer(node2.left);
        }
        return (queue1.isEmpty() && queue2.isEmpty());
    }
}

简化后:使用一个队列即可,保证连续出栈两个结点的对称结点,那么入栈的时候堆成位置一起入栈即可

class Solution {
    public boolean isSymmetric(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root.left);
        queue.offer(root.right);
        while (!queue.isEmpty()) {
            TreeNode node1 = queue.poll();
            TreeNode node2 = queue.poll();
            if (node1 == null && node2 == null) continue;
            if (node1 == null || node2 == null) return false;
            if (node1.val != node2.val) return false;
            // 对称的结点放在一起,出栈是连续的就可以判断是否相等
            queue.offer(node1.left);
            queue.offer(node2.right);
            queue.offer(node1.right);
            queue.offer(node2.left);
        }
        return true;
    }
}

100. 相同的树

Leetcode 链接
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:
输入:p = [1,2,3], q = [1,2,3]
输出:true

示例 2:
输入:p = [1,2], q = [1,null,2]
输出:false

示例 3:
输入:p = [1,2,1], q = [1,1,2]
输出:false
**题解:**同上题

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) return true;
        if (p == null || q == null) return false;
        return p.val == q.val && isSameTree(q.left, p.left) && isSameTree(p.right, q.right);
    }
}

572. 另一棵树的子树

Leetcode 链接
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例 1:
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
示例 2:

输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false
题解:
方式一:暴力递归

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    	// 先序遍历
        if (root == null) return false;
        // 如果与子树根节点相同,判断是否子树
        if (root.val == subRoot.val && dfs(root, subRoot)) return true;
        // 只有一颗相同的树就行
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);       
    }
    // 判断两棵树是否相同
    public boolean dfs(TreeNode p, TreeNode q) {
        if (p == null && q == null) return true;
        if (p == null || q == null) return false;
        return p.val == q.val && dfs(p.left, q.left) && dfs(p.right, q.right);
    }
}

104. 二叉树的最大深度

Leetcode 链接
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
题解:
方式一:迭代法
层序遍二叉树,遍历完后返回层数

class Solution {
    public int maxDepth(TreeNode root) {
        int res = 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while(!queue.isEmpty()) {
            int len = queue.size();
            res++;
            while (len-- > 0) {
                TreeNode node = queue.poll();
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return res;
    }
}

方式二:递归

class Solution {
    public int maxDepth(TreeNode root) {
        return dfs(root);
    }
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        // 返回左右子树最大深度 + 1(加一表示当前层)
		return Math.max(dfs(root.left), dfs(root.right)) + 1;
    } 
}

111. 二叉树的最小深度

Leetcode 链接
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。

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

示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
题解:
方式一:迭代法
层序遍历找到第一个没有孩子的结点

class Solution {
    public int minDepth(TreeNode root) {
        int res = 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while(!queue.isEmpty()) {
            int len = queue.size();
            res++;
            while (len-- > 0) {
                TreeNode node = queue.poll();
                // 左右孩子都为 null 
                if (node.left == null && node.right == null) return res;
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return res;
    }
}

方式二:递归
要注意 [1, 2],最小深度为 2 ,而不是 0

class Solution {
    public int minDepth(TreeNode root) {
        return dfs(root);
    }
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        // 左结点为 null 去递归右边,右结点为 null,去递归左边
        if (root.left == null) return dfs(root.right) + 1;
        if (root.right == null) return dfs(root.left) + 1;
        return Math.min(dfs(root.left), dfs(root.right)) + 1;
    } 
}

222. 完全二叉树的节点个数

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

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

示例 2:
输入:root = []
输出:0

示例 3:
输入:root = [1]
输出:1
题解: 直接递归就完事了

class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        // 左边结点数加上右边节点数加上当前结点
        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}

110. 平衡二叉树

Leetcode 链接
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

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

示例 2:
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

示例 3:
输入:root = []
输出:true

题解:
方法一:暴力递归

class Solution {
    public boolean isBalanced(TreeNode root) {
    	// 中序遍历二叉树,每个结点进行判断左右子树差值
        if (root == null) return true; 
        // 左右结点差值小于等于1,再去遍历左右结点
        return Math.abs(dfs(root.left) - dfs(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }
    // 求最大高度
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        return Math.max(dfs(root.left), dfs(root.right)) + 1;
    }
}

方法二:优化递归
因为上边的方法每个结点都遍历了两次,第二次遍历为求做大高度差,

class Solution {
    public boolean isBalanced(TreeNode root) {
        return (dfs(root) != -1);
    }
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        // 先得到左右子树高度差有,-1 表示存在高度差大于1
        int leftHight = dfs(root.left);
        if (leftHight == -1) {
            return -1;
        }
        int rightHight = dfs(root.right);
        if (rightHight == -1) {
            return -1;
        }
        // 如果左右差大于 1,放返回-1
        if (Math.abs(leftHight - rightHight) > 1) {
            return -1;
        }
        return Math.max(dfs(root.left), dfs(root.right)) + 1;
    }
}

257. 二叉树的所有路径

Leetcode 链接
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。叶子节点 是指没有子节点的节点。

示例 1:
输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]

示例 2:
输入:root = [1]
输出:[“1”]

题解: 这里用到了回溯

  1. 先序遍历二叉树
  2. 使用列表记录遍历的路径
  3. 如果左右孩子结点都为 null,表示一条路径遍历结束,添加到返回列表中
  4. 回溯的时候去重列表中的结点
class Solution {
    List<Integer> path = new ArrayList<>();
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root != null) dfs(root, res);
        return res;
    }
    public void dfs(TreeNode root, List<String> res) {
    	// 中序遍历,先添加结点值到路径列表
        path.add(root.val);
        if (root.left == null && root.right == null) {
        	// 左右孩子结点为空,一条路径遍历结束,拼接列表中的结点值
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < path.size() - 1; i++) {
                sb.append(path.get(i)).append("->");
            }
            sb.append(path.get(path.size() - 1));
            // 添加到返回值列表中
            res.add(sb.toString());
            return;
        }
		// 递归
        if (root.left != null) {
            dfs(root.left, res);
            // 回溯,每回溯一次去掉路径列表中的最后一个结点值
            path.remove(path.size() - 1);
        }
        if (root.right != null) {
            dfs(root.right, res);
            // 回溯
            path.remove(path.size() - 1);
        }
    }
}

优化:遍历的同时拼接返回的字符串,回溯时删除

class Solution {
    StringBuilder path = new StringBuilder();
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root != null) dfs(root, res);
        return res;
    }
    public void dfs(TreeNode root, List<String> res) {
        path.append(root.val);
        if (root.left == null && root.right == null) {
            res.add(String.valueOf(path));
            return;
        }
        path.append("->");
        // 记录当前结点拼接完后的字符串下标,作为回溯删除的起点
        int len = path.length();
        if (root.left != null) {
            dfs(root.left, res);
            // 回溯
            path.delete(len, path.length());
        }
        if (root.right != null) {
            dfs(root.right, res);
            // 回溯
            path.delete(len, path.length());
        }
    }
}

也可以不使用回溯:
因为 path 只有一份,递归的同时添加结点到 path 中,所以递归到一条路径结束后,需要回溯动态维护 path,回退到上一层递归时需要将 path 也回退到上一层的 path。
所以我们可以在每次进入递归的时候克隆一份 path 出来给下边的递归使用

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        StringBuilder path = new StringBuilder();
        dfs(root, res, path);
        return res;
    }
    public void dfs(TreeNode root, List<String> res, StringBuilder s) {
        if (root == null) return;
        StringBuilder path = new StringBuilder(s);
        path.append(root.val);
        if (root.left == null && root.right == null) {
            res.add(path.toString());
            return;
        }
        path.append("->");
        dfs(root.left, res, path);
        dfs(root.right, res, path);
    }
}

404. 左叶子之和

Leetcode 链接
给定二叉树的根节点 root ,返回所有左叶子之和。

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

解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
示例 2:
输入: root = [1]
输出: 0

题解: 直接遍历二叉树,难点在于怎么判断是不是左叶子结点:是通过遍历到父亲结点的时候判断的,因为自己不知道自己的父亲结点的右孩子有没有孩子结点。

class Solution {
    int sum = 0;
    public int sumOfLeftLeaves(TreeNode root) {
        dfs(root);
        return sum;
    }
    public void dfs(TreeNode root) {
        if (root == null) return;
        if (root.left != null && root.left.left == null && root.left.right == null) {
        	// 左孩子不为空,左孩子的左孩子为空,左孩子的右孩子为空,那么左孩子才是叶子结点
            sum += root.left.val;
        }
        dfs(root.left);
        dfs(root.right);
    }
}

带返回值的递归

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        return dfs(root);
    }
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        int res = 0;
        if (root.left != null && root.left.left == null && root.left.right == null) {
            res = root.left.val;
            return dfs(root.right) + res;
        }
        // 返回左右子树的左叶子结点和,加上当前结点的值(如果不是左叶子结点,res = 0)
        return dfs(root.left) + dfs(root.right) + res;
    }
}

513. 找树左下角的值

Leetcode 链接

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。

示例 1:
输入: root = [2,1,3]
输出: 1

示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
题解:
方式一:层序遍历简单,返回最下边一层的首个结点值

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        TreeNode resNode = null;
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            // 记录首结点的值(队头结点的值)
            resNode = queue.peek();
            while (len-- > 0) {
                TreeNode node = queue.poll();
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return resNode.val;
    }
}

方式二:递归
先序遍历二叉树,同时记录最大深度,当前结点深度大于最大深度时更新返回值。因为是先序遍历,同深度的结点保存的是第一个。

class Solution {
    int res = 0;
    int maxDeep = -1;
    public int findBottomLeftValue(TreeNode root) {
        dfs(root, 0);
        return res;
    }
    public void dfs(TreeNode root, int deep) {
        if (root == null) return;
        // 深度加一
        deep++;
        if (deep > maxDeep) {
        	// 大于最大深度,更新返回值和最大深度值
            res = root.val;
            maxDeep = deep;
        }
        dfs(root.left, deep);
        dfs(root.right, deep);
    }
}

112. 路径总和

Leetcode 链接
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:
输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:
输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

题解: 同 二叉树的所有路径这道题
方式一:正常递归,使用 sum 记录和

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        return dfs(root, targetSum, 0);
    }
    public boolean dfs(TreeNode root, int targetSum, int sum) {
        if (root == null) return false;
        // 更新 sum
        sum += root.val;
        if (root.left == null && root.right == null) {
        	// 相等返回 true
            return sum == targetSum;
        }
        // 只要有一个路径满足即可,是或的关系
        return dfs(root.left, targetSum, sum) || dfs(root.right, targetSum, sum);
    }
}

方式二:空间优化
不适用额外的空间记录和

class Solution {
    int sum = 0;
    public boolean hasPathSum(TreeNode root, int targetSum) {
        return dfs(root, targetSum);
    }
    public boolean dfs(TreeNode root, int targetSum) {
        if (root == null) return false;
        // targetSum 减法当前结点值
        targetSum -= root.val;
        if (root.left == null && root.right == null) {
            return targetSum == 0;
        }
        return dfs(root.left, targetSum) || dfs(root.right, targetSum);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值