二叉树基础(二)

11 篇文章 0 订阅

101. 对称二叉树

题意:给定一个二叉树,检查它是否是镜像对称的。

image-20220530202759480

1、思路:递归法

  • 判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!

  • 本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的**内侧节点和外侧节点**是否相等。

  • 因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中

  • 既然确定了本次采用后续遍历,且一个树采用左右中,另一个树采用右左中

    • 确定递归函数的参数和返回值
      因为我们需要判断根节点的两个子树是否可以翻转,所以参数为根节点的左子树和右子树boolean isSymmetric(TreeNode left, TreeNode right)

    • 确定终止条件

      • 如果左节点为空,右节点不为空,则直接返回false
      • 如果右节点为空,左节点不为空,则直接返回false
      • 如果左右节点都为空,则直接返回true
      • 如果左右节点都不为空,且左右节点值不相等,则直接返回false
      • 如果左右节点都不为空,且左右节点值都相等,则继续向下执行,注意左右节点都为空,则直接返回true

      image-20220530204357713

    • 确定单次循环逻辑

      • 比较外侧是否对称,即左子树的左节点,右子树的右节点
      • 比较内测是否对称,即左子树的右节点,右子树的左节点
      • 内外侧都对称,则返回true
package com.yzu.lee.treenode;

import java.util.LinkedList;
import java.util.Queue;

/**
 * @ClassName: IsSymmetric
 * @Description:
 * @author: Leekuangyew
 * @date: 2022/5/27 9:51
 */
public class IsSymmetric {
    public boolean isSymmetric(TreeNode root) {
        //递归法
        if (root == null) return false;
        return isSymmetric(root.left, root.right);
    }

    private boolean isSymmetric(TreeNode left, TreeNode right) {
        if (left == null && right != null) {
            return false;
        }
       if (right == null && left != null) {
            return false;
        }
        if (left == null && right == null) {
            return true;
        }
        if (left.val != right.val) {
            return false;
        }
        //为什么left和right都不为空,并且left和right的值相等,不直接相反 ,因为需要继续往下递归
        boolean outer = isSymmetric(left.left, right.right); //左子树:左、 右子树:右
        boolean inner = isSymmetric(left.right, right.left);//左子树:右、右子树:左
        return outer && inner;
    }

}

2、思路:迭代法

  • 用队列实现层次遍历
    • 创建一个队列,并将根节点的左节点和右节点分别加入队列中
    • 循环判断队列是否为空
      • 从队列中头部取出两个节点,分别赋值给左节点leftNode和右节点rightNode
      • 对左右节点进行判断
        • 如果左节点为空,右节点不为空,则直接返回false
        • 如果右节点为空,左节点不为空,则直接返回false
        • 如果左右节点都为空,则跳出本次循环,继续下次循环
        • 如果左右节点都不为空,且左右节点值不相等,则直接返回false
      • 接下来向队列中添加节点,注意顺序
        • 添加左子树的左节点,添加右子树的右节点
        • 添加左子树的右节点,添加右子树的左节点
    • 最终返回true
public boolean isSymmetric(TreeNode root) {
    //迭代法
    if (root == null) return false;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root.left);
    queue.offer(root.right);

    while (!queue.isEmpty()) {

        TreeNode leftNode = queue.poll();
        TreeNode rightNode = queue.poll();

        if (leftNode == null && rightNode != null) {
            return false;
        }
        if (rightNode == null && leftNode != null) {
            return false;
        }
        if (rightNode == null && leftNode == null) {
            continue;
        }
        if (rightNode.val != leftNode.val) {
            return false;
        }

        queue.offer(leftNode.left);
        queue.offer(rightNode.right);
        queue.offer(leftNode.right);
        queue.offer(rightNode.left);
            }
    	return true;
}

100.相同的树

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

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

image-20220601194832663

1、思路:递归法

  • 本题与对称二叉树的方法基本相同
  • 采用后续遍历,遍历两个树的遍历顺序是左右中
    • 确定递归函数的参数和返回值,boolean isSameTree(TreeNode p, TreeNode q),参数传入两个树的根节点
    • 确定终止条件
      • 如果左树节点为空,右树节点不为空,则直接返回false
      • 如果右树节点为空,左树节点不为空,则直接返回false
      • 如果左树节点和右树节点都为空,则直接返回true
      • 如果左树节点和右树节点的值不想等,则直接返回false
    • 确定单次循环逻辑
      • 比较两棵树的左子树是否相等
      • 比较两棵树的右子树是否相等
      • 左右子树都相等,则返回true
public boolean isSameTree(TreeNode p, TreeNode q) {
        //递归法
        if (p == null && q != null) {
            return false;
        }
        if (q == null && p != null) {
            return false;
        }
        if (q == null && p == null) {
            return true;
        }
        if (q.val != p.val) {
            return false;
        }

        boolean leftNode = isSameTree(p.left, q.left);
        boolean rightNode = isSameTree(p.right, q.right);
        return leftNode && rightNode;
    }

2、思路:迭代法

  • 利用队列层序遍历
  • 一开始将左树根节点和右树根节点添加到队列中
  • 当队列不为空时,就一直循环
    • 从队列中取出两个节点,分别赋值为leftNode和rightNode
    • 当左节点和右节点都为空,则跳出循环,并继续下次循环
    • 当左节点为空或者右节点为空或者左节点与右节点值不想等,直接返回false
    • 然后依次将左树和右树的左节点,左树和右树的右节点添加入队列中
public boolean isSameTree(TreeNode p, TreeNode q) {
    //迭代法
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(p);
    queue.offer(q);

    while (!queue.isEmpty()) {
        TreeNode leftNode = queue.poll();
        TreeNode rightNode = queue.poll();

        if (leftNode == null && rightNode == null) {
            continue;
        }

        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }

        queue.offer(leftNode.left);
        queue.offer(rightNode.left);
        queue.offer(leftNode.right);
        queue.offer(rightNode.right);
    }
    return true;
}

572.另一个树的子树

题意:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

image-20220601201312286

1、思路:递归法

  • 按照后序进行遍历,两个后序遍历
    • 确定递归函数的参数和返回值,boolean isSubtree(TreeNode root, TreeNode subRoot),参数是树的根节点和子树的根节点
    • 确定终止条件,当传入的节点为空时,直接返回false
    • 确定单次递归逻辑
      • 判断传入的树节点与子树是否相等,如果相等就直接返回true,if (sameTree(root, subRoot))
      • 判断两个树是否相等的方法在上一题中已经讲解
      • 分别遍历左子树与右子树是否与子树相等,最终返回left || right
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        //递归法
        if (root == null) {
            return false;
        }
        if (sameTree(root, subRoot)) {
            return true;
        }
        boolean left = isSubtree(root.left, subRoot);
        boolean right = isSubtree(root.right, subRoot);
        return left || right;
}

private boolean sameTree(TreeNode p, TreeNode q) {
        if (p == null && q != null) {
            return false;
        }
        if (q == null && p != null) {
            return false;
        }
        if (q == null && p == null) {
            return true;
        }
        if (q.val != p.val) {
            return false;
        }
        boolean leftNode = sameTree(p.left, q.left);
        boolean rightNode = sameTree(p.right, q.right);
        return leftNode && rightNode;
}

2、思路:迭代法

  • 迭代法与递归法思路完全一样
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    //迭代法
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int size = queue.size();

        while (size-- > 0) {
            TreeNode node = queue.poll();
            if (sameTree(node, subRoot)) {
                return true;
            }
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
    }
    return false;
}

public boolean sameTree(TreeNode p, TreeNode q) {
    //迭代法
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(p);
    queue.offer(q);

    while (!queue.isEmpty()) {
        TreeNode leftNode = queue.poll();
        TreeNode rightNode = queue.poll();

        if (leftNode == null && rightNode == null) {
            continue;
        }

        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }

        queue.offer(leftNode.left);
        queue.offer(rightNode.left);
        queue.offer(leftNode.right);
        queue.offer(rightNode.right);
    }
    return true;
}

104.二叉树的最大深度

题意:给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

image-20220530205800562

1、思路:递归法

  • 本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
    • 确定递归函数的参数和返回值:int maxDepth(TreeNode root),参数是传入树的根节点,返回这棵树的深度
    • 确定终止条件:如果为空节点的话,就返回0,表示高度为0。if (root == null) return 0;
    • 确定单层递归的逻辑:先求出左子树的深度,然后求出右子树的深度,最终返回左子树深度和右子树深度的较大值+1;
public int maxDepth(TreeNode root) {
        //DFS 递归
        if (root == null) return 0;
        else {
            int leftHeight = maxDepth(root.left);
            int rightHeight = maxDepth(root.right);
            return Math.max(leftHeight, rightHeight) + 1;
        }

    }

2、思路:迭代法

  • 使用迭代法的话,层序遍历最好,因为最大深度就是二叉树的层数
    • 利用队列来模拟层序遍历
    • 当队列不为空时,一直循环,每次循环深度加1
    • 最后返回深度
    /**
     * 迭代法,使用层序遍历
     */
    public int maxdepth(treenode root) {
        if(root == null) {
            return 0;
        }
        deque<treenode> deque = new linkedlist<>();
        deque.offer(root);
        int depth = 0;
        while (!deque.isempty()) {
            int size = deque.size();
            depth++;
            for (int i = 0; i < size; i++) {
                treenode poll = deque.poll();
                if (poll.left != null) {
                    deque.offer(poll.left);
                }
                if (poll.right != null) {
                    deque.offer(poll.right);
                }
            }
        }
        return depth;
    }

559.n叉树的最大深度

题意:给定一个 n 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

image-20220531152447722

1、思路:递归法

  • 确定递归函数的参数和返回值:int maxDepth(TreeNode root),参数是传入树的根节点,返回这棵树的深度
  • 确定终止条件:如果为空节点的话,就返回0,表示高度为0。if (root == null) return 0;
  • 确定单层递归的逻辑:循环遍历每一个子树,然后求出每一子树的深度,并进行比较,最终返回最大的子树深度+1;
package com.yzu.lee.treenode;

import java.util.LinkedList;
import java.util.Queue;

/**
 * @ClassName: MaxDepthN
 * @Description:给定一个 N 叉树,找到其最大深度。
 * 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
 * @author: Leekuangyew
 * @date: 2022/5/27  12:01
 */
public class MaxDepthN {
    public int maxDepth(Node root) {
        //递归法
        if (root == null) return 0;

        int depth = 0;
        for (Node child : root.children) {
            depth = Math.max(maxDepth(child), depth);
        }
        return depth + 1;
    }
}

2、思路:迭代法

  • 依然是层序遍历
public int maxDepth(Node root) {
    //迭代法
    if (root == null) return 0;
    int depth = 0;
    Queue<Node> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int size = queue.size();

        while (size-- > 0) {
            Node node = queue.poll();

            if (node.children != null) {
                for (Node child : node.children) {
                    queue.offer(child);
                }
            }
        }
        depth++;
    }
    return depth;
}

111.二叉树的最小深度

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

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

说明: 叶子节点是指没有子节点的节点。

image-20220531153205677

思路:

直觉上好像和求最大深度差不多,其实还是差不少的。

遍历顺序上依然是**后序遍历(因为要比较递归返回之后的结果),但在处理中间节点的逻辑上,最大深度很容易理解**,最小深度可有一个误区,如图:

image-20220531153403115

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

1、思路:递归法

  • 遍历顺序上依然是**后序遍历(因为要比较递归返回之后的结果)**
    • 确定递归函数的参数和返回值int minDepth(TreeNode root),参数为树的根节点,返回最小深度
    • 确定终止条件if (node == NULL) return 0;终止条件也是遇到空节点返回0,表示当前节点的高度为0。
    • 确定单层递归的逻辑
      • 如果当前节点的左子树为空,右子树不为空,则返回右子树的深度+1;
      • 如果当前节点的右子树为空,左子树不为空,则返回左子树的深度+1;
      • 如果左右子树都不为空,则返回左右子树的最小深度+1;
package com.yzu.lee.treenode;

import java.time.OffsetDateTime;

/**
 * @ClassName: MinDepth
 * @Description:给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
 * @author: Leekuangyew
 * @date: 2022/5/25  22:09
 */
public class MinDepth {
    /**
     * 递归法,相比求MaxDepth要复杂点
     * 因为最小深度是从根节点到最近**叶子节点**的最短路径上的节点数量
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftDepth = minDepth(root.left);
        int rightDepth = minDepth(root.right);
        if (root.left == null) {
            return rightDepth + 1;
        }
        if (root.right == null) {
            return leftDepth + 1;
        }
        // 左右结点都不为null
        return Math.min(leftDepth, rightDepth) + 1;
    }
}

2、思路:迭代法

  • 同样采用层序遍历
    • 创建一个队列,并放入根节点
    • 当队列不为空时,则一直循环,每次循环深度+1;
    • 当某一个节点的左右子树都为空,则为叶子节点,直接返回深度
/**
     * 迭代法,层序遍历
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        int depth = 0;
        while (!deque.isEmpty()) {
            int size = deque.size();
            depth++;
            for (int i = 0; i < size; i++) {
                TreeNode poll = deque.poll();
                if (poll.left == null && poll.right == null) {
                    // 是叶子结点,直接返回depth,因为从上往下遍历,所以该值就是最小值
                    return depth;
                }
                if (poll.left != null) {
                    deque.offer(poll.left);
                }
                if (poll.right != null) {
                    deque.offer(poll.right);
                }
            }
        }
        return depth;
    }

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

题意:给出一个完全二叉树,求出该树的节点个数。

示例 1:

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

image-20220531155826023

1、思路:递归法(普通二叉树)

  • 递归遍历的顺序依然是后序(左右中)
    • 确定递归函数的参数和返回值int countNodes(TreeNode root),参数传入根节点,返回节点数
    • 确定终止条件:if (root == null) return 0;
    • 确定单次递归逻辑:先求左子树的节点数量,再求右子树的节点数量,最终取总和再加1
    public int countNodes(TreeNode root) {
        //普通递归法
        if (root == null) return 0;

        int left = countNodes(root.left);
        int right = countNodes(root.right);
        return left + right + 1;
}

2、思路:迭代法(普通二叉树)

  • 层序遍历
public int countNodes(TreeNode root) {
       //普通迭代法
        if (root == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int result = 0;
       while (!queue.isEmpty()) {
            int size = queue.size();

            while (size-- > 0) {
                TreeNode node = queue.poll();

                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
                result++;
            }
        }
        return result;
    }

3、思路:递归法(完全二叉树)

/**
 * 针对完全二叉树的解法
 * 满二叉树的结点数为:2^depth - 1
 */
public int countNodes(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int leftDepth = getDepth(root.left);
    int rightDepth = getDepth(root.right);
    if (leftDepth == rightDepth) {// 左子树是满二叉树
        // 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
        return (1 << leftDepth) + countNodes(root.right);
    } else {// 右子树是满二叉树
        return (1 << rightDepth) + countNodes(root.left);
    }
}

private int getDepth(TreeNode root) {
    int depth = 0;
    while (root != null) {
        root = root.left;
        depth++;
    }
    return depth;
}

110.平衡二叉树

题意:给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

image-20220531201209599

1、思路:递归法

  • 看这道题目和104.二叉树的最大深度很像,其实有很大区别。

  • 这里强调一波概念:

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

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

    ==image-20220531201613688==

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

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

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

  • 所以这道题使用后序递归

    • 明确递归函数的参数和返回值int getHeight(TreeNode root) ,参数是传入节点,返回当前节点的高度
    • 明确终止条件if (root == null) return 0;递归过程中遇到空节点,则返回0
    • 确定单次递归的逻辑,分别求出左子树和右子树的高度,如果他们的差值小于等于1,则返回当前二叉树的高度,否则返回-1,已经不是平衡二叉树了
package com.yzu.lee.treenode;

import java.util.LinkedList;
import java.util.Queue;

/**
 * @ClassName: IsBalanced
 * @Description:给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
 * @author: Leekuangyew
 * @date: 2022/5/27  18:00
 */
public class IsBalanced {
    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;
        }
        //中
        if (Math.abs(rightHeight - leftHeight) > 1) {
            return -1;
        }
        return Math.max(rightHeight, leftHeight) + 1;
    }
}

257. 二叉树的所有路径

题意:给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

image-20220601001518580

1、思路:递归法

  • 要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
  • 在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
  • 前序遍历以及回溯的过程如图:

image-20220601001741343

  • 递归函数函数参数以及返回值:void traversal(TreeNode root, List<Integer> paths, List<String> result),参数需要传入根节点,记录每一条路径,以及存放结果集的result

  • 确定递归终止条件

    • 本题需要判断当前节点是否为叶子节点,如果是则停止,即**当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。**

      if (root.left == null && root.right == null) {
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < paths.size() - 1; i++) {
              sb.append(paths.get(i) + "->");
          }
          sb.append(paths.get(paths.size() - 1));
          result.add(sb.toString());
          return;
      }
      
    • 确定单层递归逻辑

      • 在递归一开始,需要把当前节点放入到Paths集合中
      • 如果当前节点 的左节点不为空,则递归,递归完之后需要进行回溯,回溯和递归是一一对应的,有一个递归,就要有一个回溯
      • 如果当前节点 的右节点不为空,则递归,递归完之后需要进行回溯,回溯和递归是一一对应的,有一个递归,就要有一个回溯
package com.yzu.lee.treenode;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: BinaryTreePaths
 * @Description:
 * @author: Leekuangyew
 * @date: 2022/5/27  18:30
 */
public class BinaryTreePaths {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<>();
        //递归+回溯
        if (root == null) return result;
        List<Integer> paths = new ArrayList<>();


        traversal(root, paths, result);
        return result;
    }

    private void traversal(TreeNode root, List<Integer> paths, List<String> result) {
        //中
        paths.add(root.val);
        if (root.left == null && root.right == null) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < paths.size() - 1; i++) {
                sb.append(paths.get(i) + "->");
            }
            sb.append(paths.get(paths.size() - 1));
            result.add(sb.toString());
            return;
        }

        //左
        if (root.left != null) {
            traversal(root.left, paths, result);
            paths.remove(paths.size() - 1);
        }
        //右
        if (root.right != null) {
            traversal(root.right, paths, result);
            paths.remove(paths.size() - 1);
        }
        return;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leekuangyee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值