1. 检查两颗树是否相同
在线OJ:100. 相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/73f01ad98fbed2855864851ddc958f69.png)
输入:p = [1,2,3], q = [1,2,3]
输出:true
示例 2:
![](https://i-blog.csdnimg.cn/blog_migrate/f99c828625164c8139fce81174a97a53.png)
输入:p = [1,2], q = [1,null,2]
输出:false
示例 3:
![](https://i-blog.csdnimg.cn/blog_migrate/9995817fd97fede9672856c8dd21ad42.png)
输入:p = [1,2,1], q = [1,1,2]
输出:false
提示:
两棵树上的节点数目都在范围 [0, 100] 内
-104 <= Node.val <= 104
🌗解题思路:
如果两棵树的根结点都为空,则两棵树相同。
如果两棵树的根结点有一个为空,则两棵树必然不相同。
如果两棵树都不为空,则判断根结点的值是否相同,不相同则这两棵树必然不相同。
如果两棵树根结点的值相同,则需要判断两棵树的左右子树是否相同,如果相同则这两棵树相同。
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 isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
return false;
}
}
2. 另一颗树的子树
在线OJ:572. 另一棵树的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true;否则,返回 false 。
二叉树 tree 的一棵子树包括tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/d21993c5a104d1be6905d11b9f7c640f.png)
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
示例 2:
![](https://i-blog.csdnimg.cn/blog_migrate/c607d8e78526453ef616fd7780aa834e.png)
输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false
提示:
root 树上的节点数量范围是 [1, 2000]
subRoot 树上的节点数量范围是 [1, 1000]
-104 <= root.val <= 104
-104 <= subRoot.val <= 104
💯解题思路:
判断一棵树是否为另一棵树的子树我们可以基于判断两棵树是否相同去做。
如果root与subRoot有一棵树是空,那么subRoot必然不是root的子树。
如果root与subRoot相同,那么subRoot肯定是root的子树。
如果root的子树含有与subRoot相同的树,那么subRoot肯定是root的子树。
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null && subRoot != null || root != null && subRoot == null) {
return false; // 1
}
if(isSameTree(root, subRoot)) {
return true; // 2
}
if(isSubtree(root.left, subRoot)) { // 3
return true;
}
if(isSubtree(root.right, subRoot)) {
return true;
}
return false;
}
//判断两棵树是否相同
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;
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
3. 二叉树最大深度
在线OJ:104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
💯解题思路:
如果根结点为空,则这棵树的高度为0,返回0。
一棵二叉树的最深深度即为左右子树深度的最大值加上1。
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) { //1
return 0;
}
int leftHight = maxDepth(root.left);
int rightHight = maxDepth(root.right); //2
return leftHight>rightHight ? leftHight+1 : rightHight+1;
}
}
4. 判断—颗二叉树是否是平衡二叉树
在线OJ:110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/bdb9395dd448065de85712f400b39d4b.png)
输入:root = [3,9,20,null,null,15,7]
输出:true
示例 2:
![](https://i-blog.csdnimg.cn/blog_migrate/946656419003faa2f3e0d6bf6af4208c.png)
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
示例 3:
输入:root = []
输出:true
提示:
树中的节点数在范围 [0, 5000] 内
-104 <= Node.val <= 104
💯解题思路:
这道题可以基于求二叉树的高度来做,
如果树为空,则这棵树是平衡二叉树。
获取该树左右子树的高度差的绝对值,如果大于1,则这棵树肯定不是平衡二叉树。
如果高度差绝对值不大于1,再判断该树的左右子树是否都是平衡二叉树,如果是,则这一整棵树是平衡二叉树。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
return maxDepth(root) >= 0;
}
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int left = maxDepth(root.left);
if(left == -1){
return -1;
}
int right = maxDepth(root.right);
if(right == -1){
return -1;
}
if(Math.abs(left - right) <= 1){
return Math.max(left , right) + 1;
} else{
return -1;
}
}
}
5. 对称二叉树
在线OJ:101. 对称二叉树
给你一个二叉树的根节点 root, 检查它是否轴对称。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/4e507f203cfe9910d646965de9e68d81.png)
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
![](https://i-blog.csdnimg.cn/blog_migrate/be7100ab56dec692c6f2cdfcc8a13dee.png)
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
树中节点数目在范围 [1, 1000] 内
-100 <= Node.val <= 100
💯解题思路:
如果这棵树为空树, 那么这棵树是平衡二叉树,返回true
如果不为空, 那么开始递归判断;
递归过程:
(1)判断左右子树两个根节点值是否相等
(2)判断 A(左) 的右子树与 B(右) 的左子树是否对称(递归)
(3)判断 A(左) 的左子树与 B 的右子树是否对称(递归)
递归结束条件:
(1)左右子树两个根节点值不相等
(2)都为空指针则返回 true
(3)只有一个为空则返回 false
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return isSymmtericChild(root.left,root.right);
}
private boolean isSymmtericChild(TreeNode left, TreeNode right) {
if(left == null && right != null || left != null && right == null){
return false;
}
if(left == null && right == null){
return true;
}
if(left.val == right.val){
return isSymmtericChild(left.left,right.right) && isSymmtericChild(left.right,right.left);
}
return false;
}
}
6. 二叉树的构建及遍历
在线OJ:KY11 二叉树遍历
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:
输入包括1行字符串,长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。
示例:
输入:abc##de#g##f###
输出:c b e g d f a
💯解题思路:
要注意到题目需要多组输入;
先构建二叉树结点, 包含char类型的数值域, 左右孩子节点的引用;
遍历字符串,根据前序遍历的顺序构建二叉树; 遇到非#符号,创建一个结点并存入目标值,然后按照前序遍历的顺序构建该树的左子树,右子树,遇到#符号,相当于遇到二叉树的空,将字符串下标加1返回即可。
使用一个成员变量,对字符串进行遍历,因为在递归的过程中,设置局部变量会被销毁 ; 而且每构建一个二叉树(遍历完一个字符串)应将该成员变量再次设置为0。
中序遍历输出已经创建好的二叉树。
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static class TreeNode{
private char val;
private TreeNode left;
private TreeNode right;
public TreeNode(char val) {
this.val = val;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 1
String str = in.nextLine();
TreeNode root = createTree(str); // 3
inOrder(root);
}
}
public static int i = 0;
public static TreeNode createTree(String s){
TreeNode root = null;
if(s.charAt(i) != '#'){
root = new TreeNode(s.charAt(i));
i++;
root.left = createTree(s);
root.right = createTree(s);
} else {
i++;
}
return root;
}
public static void inOrder(TreeNode root){
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
}
7. 二叉树的分层遍历
在线OJ:102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/1d235ab7cb0cb3d4acac7a12579b4ac5.png)
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
💯解题思路:
实现层序遍历,我们可以设计一个队列来实现, 首先将根节点入队,然后循环每次将队头元素出队(出队时获取到节点中的元素)同时将出队节点的左右孩子结点(不包括空结点)依次入队,以此类推,直到最终队列和当前结点均为空时,表示遍历结束。
看题目中的输出示例, 让我们返回的是一个列表, 列表中的每个元素还是一个列表(存放每一层的数据); 也就是说我们需要将每层的元素分开; 那么这里用顺序表来实现再合适不过, list是要返回的表, tmp表中放一层中的数据, 每次遍历完一层就将tmp添加到list中。
我们可以在每次获取出队元素前,先获取队列中元素个数,这个元素个数就是当前层次的元素个数,这样就能统计每层的数据, 将每层的数据分隔开来。
记录当前队列元素个数,即二叉树每层的元素个数。
将此层二叉树的结点的左右非空孩子结点存于队列中,并将该层二叉树结点的值存于顺序表中。
将非空的子节点存入队列中。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if(root == null){
return list;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> tmp = new ArrayList<>();
int size = queue.size();
while(size != 0){
size--;
TreeNode cur = queue.poll();
tmp.add(cur.val);
if(cur.left != null){
queue.offer(cur.left);
}
if(cur.right != null){
queue.offer(cur.right);
}
}
list.add(tmp);
}
return list;
}
}
8. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先
在线OJ:236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
示例 1:
![](https://i-blog.csdnimg.cn/blog_migrate/67fbb01312909f73805c4d6ea9fcdedd.png)
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
![](https://i-blog.csdnimg.cn/blog_migrate/1af749a704fab3aadeec71dc6363a892.png)
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
树中节点数目在范围 [2, 105] 内。
-109 <= Node.val <= 109
所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。
💯解题思路:
思路1: 非递归实现
可以利用两个栈分别存储root->p和root->q的二叉树路径,然后将元素多的那一个栈内的元素出栈,直到与另一个栈的元素数量相等,最后两栈同时出栈并比较出栈的元素,如果相等,则相等的那个结点就是p,q的最近公共祖先,最后如果栈为空了还没有找到,p,q在root这棵树上没有公共祖先。
使用两个栈分别存储root->p,和root->q的路径。
寻找root->p, root->q的二叉树路径。
将元素数量较多的栈出栈,使其元素数量与另一个栈相等。
两栈同时出栈并比较,相等的元素结点即为最近公共结点。
关于 2. 寻找root->p和root->q路径的思路(递归):
2.1 如果二叉树为空或者目标节点为空,不存在路径。
2.2 创建一个栈,用于存放路径,先假设路径存在并把这条路径上的结点入栈,然后顺着这个路径寻找p或者q,如果没找到,将这条路径上的结点出栈,换另外一条路径寻找。
2.3 函数返回值为boolean,以判断是否找到正确的路径。
思路2: 递归实现
如果为空树返回null。
如果p,q结点均与根结点root相同,则最近公共祖先为root。
如果p,q都在左子树或右子树,则最近公共祖先也在左子树或右子树; 此时p和q的最近公共祖先为p或者q, 具体看先碰到p还是q
如果p,q位于不同的左右子树上,则最近公共祖先为root。
对于3和4两点,我们可以分别去左右子树找p,q结点,如果在左右子树均找到,说明4成立,如果在左右子树中只找到一个,就说明3成立,如果在左右子树都没有找到,就说明p,q在root这棵二叉树上没有公共祖先。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
if(root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null){
return root;
} else if(left != null){
return left;
} else if (right != null){
return right;
} else{
return null;
}
}
}
/*
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Stack<TreeNode> stack1 = new Stack<>();
getPath(root,p,stack1);
Stack<TreeNode> stack2 = new Stack<>();
getPath(root,q,stack2);
int size1 = stack1.size();
int size2 = stack2.size();
if(size1 > size2){
int size = size1 - size2;
while(size > 0){
stack1.pop();
size--;
}
} else {
int size = size2 - size1;
while(size > 0){
stack2.pop();
size--;
}
}
while(stack1.peek() != stack2.peek()){
stack1.pop();
stack2.pop();
}
return stack1.peek();
}
private boolean getPath(TreeNode root,TreeNode node,Stack<TreeNode> stack){
if(root == null || node == null){
return false;
}
stack.push(root);
if(root == node){
return true;
}
boolean flg1 = getPath(root.left,node,stack);
if(flg1 == true){
return true;
}
boolean flg2 = getPath(root.right,node,stack);
if(flg2 == true){
return true;
}
stack.pop();
return false;
}