数据结构_树的练习题

 一、二叉树的中序遍历

中序遍历是二叉树遍历的一种方式,它的遍历顺序是先遍历左子树,然后访问根节点,最后遍历右子树。对于给定的二叉树的根节点root,可以使用递归或者迭代的方式来实现中序遍历。

递归实现:

①建立一个TreeNode类,

这个类表示二叉树的节点,具有以下属性和方法:

  1. 属性:

    • val:表示节点的值,类型为int。
    • left:表示节点的左子节点,类型为TreeNode。
    • right:表示节点的右子节点,类型为TreeNode。
  2. 构造方法:

    • TreeNode():无参构造方法,用于创建一个空的TreeNode对象。
    • TreeNode(int val):带有一个参数的构造方法,用于创建一个具有指定值的TreeNode对象。
    • TreeNode(int val, TreeNode left, TreeNode right):带有三个参数的构造方法,用于创建一个具有指定值、左子节点和右子节点的TreeNode对象。

通过这个TreeNode类,我们可以创建二叉树的节点,并且可以通过节点的属性来访问和操作节点的值、左子节点和右子节点。

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;

    }
}

②建立一个Solution类,

首先,代码创建了一个空的整数列表result,用于存储中序遍历的结果。

然后,通过递归调用inorder方法实现中序遍历。inorder方法接收一个节点和结果列表作为参数。

inorder方法中,首先判断当前节点是否为空,如果为空则直接返回。然后,递归调用inorder方法遍历当前节点的左子树,将结果添加到结果列表中。接着,将当前节点的值添加到结果列表中。最后,递归调用inorder方法遍历当前节点的右子树,将结果添加到结果列表中。

最后,返回结果列表。

这样,通过递归调用inorder方法,可以实现对整个二叉树的中序遍历。

import java.util.ArrayList;
import java.util.List;


public class Solution1 {
    //接收一个二叉树的根节点作为参数,返回一个按照中序遍历顺序排列的整数列表
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();//创建一个空的整数列表用于存储中序遍历的结果
        if (root == null) {
            return result;
        }
        //调用inorder方法实现中序遍历,inorder方法接收一个节点和结果列表作为参数
        inorder(root.left, result);
        result.add(root.val);
        inorder(root.right, result);
        return result;
    }

    private void inorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        inorder(node.left, result);
        result.add(node.val);
        inorder(node.right, result);
    }

    public static void main(String[] args) {

    }
}

二、不同的二叉搜索树Ⅰ

生成所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同二叉搜索树,可以使用递归的方法来解决。

首先,我们需要明确二叉搜索树的性质:对于任意一个节点,它的左子树的所有节点的值都小于它的值,而右子树的所有节点的值都大于它的值。

我们可以通过遍历每个节点作为根节点,然后递归生成左子树和右子树的方式来构建二叉搜索树。如下:

  1. 如果 n 为 0,表示没有节点,返回一个空列表。
  2. 如果 n 为 1,表示只有一个节点,返回一个只包含该节点的列表。
  3. 遍历 1 到 n,将当前节点 i 作为根节点:
    • 生成所有可能的左子树列表,即递归调用函数生成 1 到 i-1 个节点的二叉搜索树。
    • 生成所有可能的右子树列表,即递归调用函数生成 i+1 到 n 个节点的二叉搜索树。
    • 将左子树列表和右子树列表进行两两组合,每一对左子树和右子树都可以构成一个以 i 为根节点的二叉搜索树。
    • 将所有以 i 为根节点的二叉搜索树添加到结果列表中。

最后,返回结果列表即可。

import java.util.ArrayList;
import java.util.List;

public class Solution2 {
    public List<TreeNode> generateTrees(int n) { //生成所有可能的二叉搜索树
        if (n == 0) {
            return new ArrayList<>();
        }
        return generateTrees(1, n);
    }

    private List<TreeNode> generateTrees(int start, int end) {//生成从start到end范围内的所有可能的二叉搜索树
        List<TreeNode> result = new ArrayList<>();
        //判断如果start大于end,说明当前范围内没有节点,将null添加到result中,并返回result
        if (start > end) {
            result.add(null);
            return result;
        }
        for (int i = start; i <= end; i++) {//从start到end遍历每个节点的值
            List<TreeNode> leftTrees = generateTrees(start, i - 1);//调用generateTrees方法递归地生成左子树和右子树的所有可能的二叉搜索树
            List<TreeNode> rightTrees = generateTrees(i + 1, end);
            //嵌套循环遍历左子树和右子树的所有可能的组合
            for (TreeNode left : leftTrees) {
                //在循环中,创建一个新的TreeNode节点,将当前节点的值赋给它,并将左子树和右子树连接到新节点上
                for (TreeNode right : rightTrees) {
                    TreeNode root = new TreeNode(i);
                    root.left = left;
                    root.right = right;
                    //将新节点添加到result中
                    result.add(root);
                }
            }
        }
        return result;//返回可能的二叉搜索树的列表
    }

    public static void main(String[] args) {

    }
}

其中:调用 generateTrees 方法来生成所有符合条件的二叉搜索树。

例如,调用 generateTrees(3) 将返回一个列表,其中包含以下五个二叉搜索树:

 二叉搜索树是一种特殊的二叉树,它满足左子树的所有节点的值都小于根节点的值,右子树的所有节点的值都大于根节点的值。

三、不同的二叉搜索树Ⅱ

这是一道经典的动态规划问题,可以使用动态规划的方法来解决。

首先,我们可以观察到,对于一个二叉搜索树,如果以某个节点 i 作为根节点,那么左子树的节点值范围为 1 到 i-1,右子树的节点值范围为 i+1 到 n。因此,以节点 i 作为根节点的二叉搜索树的种数,等于左子树的种数乘以右子树的种数。

接下来,我们可以定义一个数组 dp,其中 dp[i] 表示由 i 个节点组成的二叉搜索树的种数。根据上面的观察,我们可以得到状态转移方程:

dp[i] = dp * dp[i-1] + dp * dp[i-2] + … + dp[i-1] * dp

其中,dp 表示空树,只有一种情况,即 dp = 1;dp 表示只有一个节点的树,也只有一种情况,即 dp = 1。

最终,我们可以通过填充 dp 数组来求解问题,最终的结果就是 dp[n]。

public class Solution3 {
    public int numTrees(int n) {
        //创建一个长度为n+1的整型数组dp,用于存储每个节点数对应的二叉搜索树的数量
        int[] dp = new int[n + 1];
        dp[0] = 1;//当节点数为0时,为空树
        dp[1] = 1;//当节点数为1时,只有根节点

        for (int i = 2; i <= n; i++) {//从节点数为2开始遍历到n
            for (int j = 0; j < i; j++) {//对于每个节点数i,遍历从0到i-1的所有可能的左子树节点数
                dp[i] += dp[j] * dp[i - j - 1];//将左子树节点数为j,右子树节点数为i-j-1的二叉搜索树的数量相乘,并累加到dp[i]中
            }
        }

        return dp[n];//返回节点数为n的二叉搜索树的数量
    }
    public static void main(String[] args) {

    }
}

四、交错字符串 

  1. 首先,代码中定义了一个公共方法isInterleave,该方法接受三个参数:s1、s2和s3,分别表示待判断的两个字符串和目标字符串。

  2. 接下来,代码获取字符串s1和s2的长度,并判断它们的长度之和是否等于字符串s3的长度。如果不相等,则返回false,表示s1和s2无法交错组成s3。

  3. 然后,代码创建一个二维布尔数组dp,大小为(m+1)×(n+1),其中m和n分别为s1和s2的长度。dp[i][j]表示s1的前i个字符和s2的前j个字符是否能够交错组成s3的前(i+j)个字符。

  4. 接着,代码将dp设置为true,表示空字符串可以交错组成空字符串。

  5. 然后,代码使用两个循环遍历s1和s2的每个字符,分别判断当前字符是否与s3的对应位置的字符相等,并且前一个位置的dp值为true。如果满足条件,则将当前位置的dp值设置为true。

  6. 最后,代码使用两个嵌套循环遍历dp数组的每个位置,判断当前位置的字符是否与s3的对应位置的字符相等,并且前一个位置的dp值为true。如果满足条件,则将当前位置的dp值设置为true。

  7. 最后,代码返回dp[m][n],表示s1和s2是否能够交错组成s3。

这段代码使用动态规划的思想,通过填充dp数组来判断s1和s2是否能够交错组成s3。具体的实现细节可以参考代码中的注释

class Solution4 {
    //定义一个公共方法isInterleave,该方法接受三个参数:s1、s2和s3,分别表示待判断的两个字符串和目标字符串
    public boolean isInterleave(String s1, String s2, String s3) {
        //代码获取字符串s1和s2的长度
        int m = s1.length();
        int n = s2.length();
        //判断它们的长度之和是否等于字符串s3的长度。如果不相等,则返回false,表示s1和s2无法交错组成s3
        if (m + n != s3.length()) {
            return false;
        }

        //创建一个二维布尔数组dp,大小为(m+1)×(n+1),其中m和n分别为s1和s2的长度
        boolean[][] dp = new boolean[m + 1][n + 1];
        //dp[i][j]表示s1的前i个字符和s2的前j个字符是否能够交错组成s3的前(i+j)个字符
        dp[0][0] = true;//将dp设置为true,表示空字符串可以交错组成空字符串

        for (int i = 1; i <= m; i++) {//循环遍历s1的每个字符,判断是否与s3的对应位置的字符相等,并且前一个位置的dp值为true
            if (s1.charAt(i - 1) == s3.charAt(i - 1) && dp[i - 1][0]) {
                dp[i][0] = true;
            }
        }

        for (int j = 1; j <= n; j++) {//循环遍历s2的每个字符,判断是否与s3的对应位置的字符相等,并且前一个位置的dp值为true
            if (s2.charAt(j - 1) == s3.charAt(j - 1) && dp[0][j - 1]) {
                dp[0][j] = true;
            }
        }

        //循环遍历s1和s2的每个字符,分别判断当前字符是否与s3的对应位置的字符相等,并且前一个位置的dp值为true
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if ((s1.charAt(i - 1) == s3.charAt(i + j - 1) && dp[i - 1][j]) ||
                        (s2.charAt(j - 1) == s3.charAt(i + j - 1) && dp[i][j - 1])) {
                    dp[i][j] = true;
                }
            }
        }

        return dp[m][n];
    }

    public static void main(String[] args) {

    }
}

 五、验证二叉搜索树

public class Solution5 {
    public boolean isValidBST(TreeNode root) {//建立方法接收一个名为root的TreeNode类型参数,并返回一个布尔值
        //调用了isValidBST方法的重载版本,传入了root节点以及两个null值作为参数,并返回结果
        return isValidBST(root, null, null);
    }

    //接收一个名为node的TreeNode类型参数,以及两个名为min和max的Integer类型参数,并返回一个布尔值
    private boolean isValidBST(TreeNode node, Integer min, Integer max) {
        if (node == null) {//判断当前节点是否为空,如果为空,则返回true
            return true;
        }
        //判断当前节点的值是否小于等于min或者大于等于max,如果满足条件,则返回false
        if ((min != null && node.val <= min) || (max != null && node.val >= max)) {
            return false;
        }
        //递归调用isValidBST方法,分别传入当前节点的左子节点、min和当前节点的值作为参数,并将结果与递归调用isValidBST方法
        // 分别传入当前节点的右子节点、当前节点的值和max作为参数的结果进行逻辑与操作,并返回结果。
        return isValidBST(node.left, min, node.val) && isValidBST(node.right, node.val, max);
    }

    public static void main(String[] args) {

    }
}

 六、恢复二叉搜索树

要恢复二叉搜索树中被错误地交换的两个节点的值,可以使用中序遍历来解决。中序遍历二叉搜索树会得到一个递增的节点值序列。如果有两个节点的值被错误地交换,那么在中序遍历序列中会存在两个位置上的节点值不满足递增关系。

具体的解决步骤如下:

  1. 定义三个指针:first、second和prev。其中,first指向第一个错误节点,second指向第二个错误节点,prev指向当前节点的前一个节点。
  2. 对二叉搜索树进行中序遍历,判断当前节点与前一个节点的值大小关系。
    • 如果prev节点的值大于当前节点的值,并且first指针为空,将first指针指向prev节点,即第一个错误节点。
    • 如果prev节点的值大于当前节点的值,并且first指针不为空,将second指针指向当前节点,即第二个错误节点。
  3. 遍历完成后,交换第一个错误节点和第二个错误节点的值,恢复二叉搜索树的正确性

class Solution6 {
    TreeNode first = null;
    TreeNode second = null;
    TreeNode prev = null;

    public void recoverTree(TreeNode root) {
        // 中序遍历二叉搜索树
        inorderTraversal(root);
        // 交换两个错误节点的值
        int temp = first.val;
        first.val = second.val;
        second.val = temp;
    }

    private void inorderTraversal(TreeNode node) {
        if (node == null) {
            return;
        }
        inorderTraversal(node.left);
        // 判断当前节点与前一个节点的值大小关系
        if (prev != null && prev.val > node.val) {
            if (first == null) {
                first = prev;
            }
            second = node;
        }
        prev = node;
        inorderTraversal(node.right);
    }
}

 通过中序遍历找到了被错误交换的两个节点,并进行了值的交换,从而恢复了二叉搜索树的正确性。

 七、相同的树

要检验两棵二叉树是否相同,我们可以使用递归的方法来比较它们的结构和节点的值。具体的实现可以按照以下步骤进行:

  1. 首先判断两个根节点是否都为空,如果都为空,则认为它们相同,返回true。
  2. 如果其中一个根节点为空而另一个不为空,或者它们的值不相等,则认为它们不相同,返回false。
  3. 如果两个根节点都不为空且值相等,则递归地比较它们的左子树和右子树。即调用递归函数来比较p的左子树和q的左子树,以及p的右子树和q的右子树。
  4. 如果左右子树的比较结果都为true,则认为两棵树相同,返回true;否则返回false。
public class Solution7 {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 判断两个根节点是否都为空
        if (p == null && q == null) {
            return true;
        }
        // 判断其中一个根节点为空而另一个不为空,或者它们的值不相等
        if (p == null || q == null || p.val != q.val) {
            return false;
        }
        // 递归比较左子树和右子树
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }

    public static void main(String[] args) {

    }
}

这样,我们就可以使用isSameTree函数来检验两棵树是否相同了。

八、对称二叉树 

要检查一个二叉树是否轴对称,可以使用递归的方法进行判断。具体步骤如下:

  1. 创建一个递归函数 isSymmetric(TreeNode root),用于判断以 root 为根节点的二叉树是否轴对称。
  2. 在递归函数中,判断当前节点的左右子树是否对称。如果左右子树都为空,则返回 true。如果左右子树只有一个为空,或者左右子树的值不相等,则返回 false
  3. 如果左右子树都不为空且值相等,则递归调用 isSymmetric 函数,分别传入左子树的左孩子和右子树的右孩子,以及左子树的右孩子和右子树的左孩子。
  4. 如果递归调用返回的结果都为 true,则说明以 root 为根节点的二叉树是轴对称的,返回 true;否则返回 false
public class Solution8 {
    //定义了一个公共方法isSymmetric,该方法接收一个名为root的TreeNode类型参数,并返回一个布尔值。
    public boolean isSymmetric(TreeNode root) {
        //如果传入的root为null,表示当前节点为空,直接返回true
        if (root == null) {
            return true;
        }
        return isSymmetric(root.left, root.right);//调用私有方法isSymmetric,传入root的左子树和右子树作为参数
    }

    //定义了一个私有方法isSymmetric,该方法接收两个TreeNode类型的参数left和right,并返回一个布尔值。
    private boolean isSymmetric(TreeNode left, TreeNode right) {
        //如果左子树和右子树都为空,表示当前节点为空,返回true
        if (left == null && right == null) {
            return true;
        }
        //如果左子树为空或右子树为空或左子树的值不等于右子树的值,表示当前节点不对称,返回false。
        if (left == null || right == null || left.val != right.val) {
            return false;
        }
        //递归调用isSymmetric方法,分别传入左子树的左子树和右子树的右子树,以及左子树的右子树和右子树的左子树作为参数,并返回两次递归调用的结果的逻辑与运算。
        return isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left);
    }

    public static void main(String[] args) {

    }
}

九、二叉树的层序遍历

要实现二叉树的层序遍历,可以使用队列来辅助实现。具体步骤如下:

  1. 首先,创建一个队列,并将根节点入队。
  2. 进入循环,直到队列为空:
    • 从队列中取出一个节点,并将其值添加到结果列表中。
    • 如果该节点有左子节点,则将左子节点入队。
    • 如果该节点有右子节点,则将右子节点入队。
  3. 返回结果列表。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Solution9 {
    //接收一个TreeNode类型的参数root,并返回一个List<Integer>类型的结果
    //用于实现二叉树的层序遍历。层序遍历是一种广度优先搜索的算法,它按照从上到下、从左到右的顺序遍历二叉树的每个节点。
    public List<Integer> levelOrder(TreeNode root) {
        //首先创建了一个空的ArrayList对象result,用于存储遍历结果。然后,我们判断根节点root是否为空,如果为空,则直接返回空的结果列表
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        //创建了一个队列queue,用于存储待遍历的节点。我们将根节点root加入到队列中。
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        //使用while循环来遍历队列。在每次循环中,我们从队列中取出一个节点node,并将其值加入到结果列表result中。
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            result.add(node.val);

            //判断节点node的左子节点和右子节点是否为空,如果不为空,则将它们加入到队列中,以便后续遍历。
            if (node.left != null) {
                queue.offer(node.left);
            }

            //当队列为空时,表示遍历结束,我们将结果列表result返回
            if (node.right != null) {
                queue.offer(node.right);
            }
        }

        return result;
    }

    public static void main(String[] args) {

    }
}

这段代码实现了二叉树的层序遍历算法,通过使用队列来辅助遍历,可以按照层次的顺序输出二叉树的节点值

十、二叉树的锯齿形层序遍历

 

 

要实现二叉树的锯齿形层序遍历,可以使用广度优先搜索(BFS)算法。具体步骤如下:

  1. 首先创建一个队列,用于存储待遍历的节点。
  2. 将根节点入队。
  3. 创建一个布尔变量leftToRight,用于标记当前层的遍历方向。初始值为true,表示从左往右遍历。
  4. 创建一个双端队列(Deque),用于存储当前层的节点值。
  5. 当队列不为空时,执行以下操作:
    • 获取当前队列的大小,表示当前层的节点个数。
    • 创建一个临时列表,用于存储当前层的节点值。
    • 遍历当前层的节点:
      • 如果leftToRighttrue,从队列的头部取出节点,并将其值添加到临时列表的尾部。
      • 如果leftToRightfalse,从队列的尾部取出节点,并将其值添加到临时列表的头部。
    • 将临时列表添加到双端队列中。
    • 将当前层的节点的左右子节点(如果存在)依次入队。
    • 切换leftToRight的值,以改变下一层的遍历方向。
  6. 返回双端队列中的所有节点值。
    import java.util.*;
    
    public class Solution10 {
        public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
            // 创建一个空的List<List<Integer>>对象,用于存储结果
            List<List<Integer>> result = new ArrayList<>();
            // 如果根节点为空,直接返回空结果
            if (root == null) {
                return result;
            }
    
            //创建一个队列,使用LinkedList实现,并指定泛型为TreeNode
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);//将根节点root加入队列
            //定义一个boolean类型的变量leftToRight,用于标记当前层的遍历方向,初始值为true
            boolean leftToRight = true;
    
            //进入循环,直到队列为空
            while (!queue.isEmpty()) {
                int size = queue.size();//获取当前队列的大小
                Deque<Integer> level = new LinkedList<>();//创建一个双端队列level,用于存储当前层的节点值
    
                //遍历当前层的节点
                for (int i = 0; i < size; i++) {
                    TreeNode node = queue.poll();//从队列中取出一个节点
    
                    //根据leftToRight的值,将节点值加入level队列的末尾或者开头
                    if (leftToRight) {
                        //如果leftToRight为true,将节点值加入level队列的末尾
                        level.offerLast(node.val);
                    } else {
                        //如果leftToRight为false,将节点值加入level队列的开头
                        level.offerFirst(node.val);
                    }
    
                    //如果当前节点的左子节点不为空,将左子节点加入队列
                    if (node.left != null) {
                        queue.offer(node.left);
                    }
                    //如果当前节点的右子节点不为空,将右子节点加入队列
                    if (node.right != null) {
                        queue.offer(node.right);
                    }
                }
                //将当前层的节点值列表level转换为ArrayList,并加入结果列表result
                result.add(new ArrayList<>(level));
                //切换leftToRight的值,以改变下一层的遍历方向
                leftToRight = !leftToRight;
            }
    
            return result;
        }
    
        public static void main(String[] args) {
    
        }
    }

    按层遍历二叉树,并将每一层的节点值存储在一个二维列表中,其中每个子列表表示一层的节点值。最后返回这个二维列表。

十一、二叉树的最大深度

要计算二叉树的最大深度,可以使用递归的方式来实现。递归的思路是,对于每个节点,其最大深度等于其左子树和右子树的最大深度中的较大值加1。如果节点为空,则返回0。

public class Solution11 {
    public int maxDepth(TreeNode root) {//接收一个TreeNode类型的参数root,表示二叉树的根节点
        //判断根节点是否为空,如果为空,则返回0。
        if (root == null) {
            return 0;
        }
        //分别递归计算左子树和右子树的最大深度
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        //取两者中的较大值加1作为当前节点的最大深度
        return Math.max(leftDepth, rightDepth) + 1;
    }

    public static void main(String[] args) {

    }
}

十二、从前序与中序遍历序列构造二叉树

要构造二叉树并返回其根节点,可以使用递归的方式来解决这个问题。首先,我们需要明确先序遍历和中序遍历的特点:

  1. 先序遍历的第一个元素是根节点。
  2. 在中序遍历中,根节点的左边是左子树的节点,根节点的右边是右子树的节点。

基于以上特点,我们可以进行如下步骤来构造二叉树:

  1. 从先序遍历中取出第一个元素作为根节点。
  2. 在中序遍历中找到根节点的位置,将中序遍历分为左子树和右子树。
  3. 根据左子树和右子树的节点数量,将先序遍历分为左子树和右子树。
  4. 递归构造左子树和右子树,并将左右子树连接到根节点上。
public class Solution12 {
    //定义一个名为buildTree的公共方法,该方法接受两个参数
    // 分别是前序遍历数组preorder和中序遍历数组inorder,并返回一个TreeNode类型的结果。
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        //调用了私有方法build,并将前序遍历数组preorder、中序遍历数组inorder以及相关的索引作为参数传递给build方法,并返回build方法的结果
        return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    //定义了一个名为build的私有方法,该方法接受六个参数,
    // 分别是前序遍历数组preorder、前序遍历的起始索引preStart、前序遍历的结束索引preEnd、
    // 中序遍历数组inorder、中序遍历的起始索引inStart、中序遍历的结束索引inEnd,并返回一个TreeNode类型的结果。
    private TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
        //判断如果前序遍历的起始索引大于结束索引,或者中序遍历的起始索引大于结束索引,则返回null,表示当前子树为空
        if (preStart > preEnd || inStart > inEnd) {
            return null;
        }

        //获取前序遍历数组中起始索引位置的值,作为当前子树的根节点的值
        int rootVal = preorder[preStart];
        //创建一个新的TreeNode对象,将根节点的值作为参数传递给构造函数,创建根节点
        TreeNode root = new TreeNode(rootVal);

        //在中序遍历数组中找到根节点的索引位置,将其赋值给rootIndex变量
        int rootIndex = 0;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }

        //计算左子树的节点数量,即根节点在中序遍历数组中的索引位置减去中序遍历的起始索引
        int leftSize = rootIndex - inStart;

        //递归调用build方法构建左子树,传递的参数为前序遍历数组、前序遍历的起始索引加1、前序遍历的起始索引加左子树节点数量
        // 中序遍历数组、中序遍历的起始索引、根节点在中序遍历数组中的索引减1,并将返回的结果赋值给根节点的左子节点。
        root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, rootIndex - 1);
        //递归调用build方法构建右子树,传递的参数为前序遍历数组、前序遍历的起始索引加左子树节点数量加1
        // 前序遍历的结束索引、中序遍历数组、根节点在中序遍历数组中的索引加1、中序遍历的结束索引,并将返回的结果赋值给根节点的右子节点
        root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, rootIndex + 1, inEnd);

        return root;
    }

    public static void main(String[] args) {

    }
}

 buildTree方法接收先序遍历数组和中序遍历数组,并调用build方法进行递归构造二叉树。build方法接收先序遍历数组的起始位置和结束位置,中序遍历数组的起始位置和结束位置,然后根据先序遍历和中序遍历的特点进行构造。

十三、从中序与后序遍历序列构造二叉树

 

要构造二叉树,我们可以利用中序遍历和后序遍历的特点来递归构建。

首先,我们需要明确中序遍历和后序遍历的特点:

  • 中序遍历的顺序是左子树 -> 根节点 -> 右子树
  • 后序遍历的顺序是左子树 -> 右子树 -> 根节点

根据后序遍历的特点,我们可以知道后序遍历的最后一个元素一定是根节点。然后,我们可以在中序遍历中找到根节点的位置,将中序遍历分为左子树和右子树。再根据左子树和右子树的长度,我们可以在后序遍历中找到左子树和右子树的位置。

接下来,我们可以递归构建左子树和右子树。具体步骤如下:

  1. 如果中序遍历的长度为0,说明已经没有节点可以构建了,返回null。
  2. 在后序遍历中找到根节点,即后序遍历的最后一个元素。
  3. 在中序遍历中找到根节点的位置,将中序遍历分为左子树和右子树。
  4. 根据左子树和右子树的长度,在后序遍历中找到左子树和右子树的位置。
  5. 递归构建左子树和右子树,分别将左子树和右子树的中序遍历和后序遍历传入递归函数。
  6. 将根节点的值设置为后序遍历的最后一个元素。
  7. 返回根节点。
    import javax.imageio.stream.ImageInputStreamImpl;
    
    public class Solution13 {
        //调用私有方法buildTreeHelper来完成实际的构建过程,并返回构建好的二叉树的根节点
        public TreeNode buildTree(int[] inorder, int[] postorder) {//分别表示中序遍历和后序遍历的结果
            return buildTreeHelper(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
        }
    
        //递归构建二叉树
        //inorder表示中序遍历的结果,postorder表示后序遍历的结果,inStart和inEnd表示当前子树在中序遍历结果中的起始和结束位置
        // postStart和postEnd表示当前子树在后序遍历结果中的起始和结束位置
        private TreeNode buildTreeHelper(int[] inorder, int inStart, int inEnd,
                                         int[] postorder, int postStart, int postEnd) {
            //判断inStart是否大于inEnd,如果是,则说明当前子树为空,直接返回null
            if (inStart > inEnd) {
                return null;
            }
    
            //从postorder中获取根节点的值rootVal
            int rootVal = postorder[postEnd];
            //创建一个新的TreeNode对象root,并将rootVal赋值给root的值
            TreeNode root = new TreeNode(rootVal);
    
            int rootIndex = 0;
            //通过遍历inorder数组,找到rootVal在其中的索引rootIndex
            for (int i = inStart; i <= inEnd; i++) {
                if (inorder[i] == rootVal) {
                    rootIndex = i;
                    break;
                }
            }
    
            //计算左子树和右子树的节点数量
            //rootIndex是根节点在中序遍历中的索引,inStart和inEnd分别是中序遍历的起始和结束索引
            int leftSize = rootIndex - inStart;
            int rightSize = inEnd - rootIndex;
    
            //buildTreeHelper是一个辅助函数
            // 它接受中序遍历数组inorder、中序遍历的起始和结束索引inStart和inEnd、后序遍历数组postorder、后序遍历的起始和结束索引postStart和postEnd作为参数
            //左子树的中序遍历范围是inStart到rootIndex - 1,后序遍历范围是postStart到postStart + leftSize - 1
            root.left = buildTreeHelper(inorder, inStart, rootIndex - 1, postorder, postStart, postStart + leftSize - 1);
            // 右子树的中序遍历范围是rootIndex + 1到inEnd,后序遍历范围是postEnd - rightSize到postEnd - 1
            root.right = buildTreeHelper(inorder, rootIndex + 1, inEnd, postorder, postEnd - rightSize, postEnd - 1);
    
            return root;
        }
    
        public static void main(String[] args) {
    
        }
    }

    根据给定的中序遍历和后序遍历数组构建一棵二叉树。通过递归的方式,不断地划分子树的范围,并构建子树的根节点和左右子树。最终返回整棵二叉树的根节点

 十四、二叉树的层序遍历Ⅱ

 

 

要实现二叉树的自底向上层序遍历,可以使用广度优先搜索(BFS)算法:

  1. 首先创建一个队列,用于存储待遍历的节点。
  2. 将根节点入队。
  3. 创建一个列表,用于存储每一层的节点值。
  4. 进入循环,直到队列为空:
    • 在每一层开始之前,记录当前队列的大小,即当前层的节点数。
    • 创建一个临时列表,用于存储当前层的节点值。
    • 遍历当前层的节点数次:
      • 出队一个节点,并将其值添加到临时列表中。
      • 如果该节点有左子节点,则将左子节点入队。
      • 如果该节点有右子节点,则将右子节点入队。
    • 将临时列表添加到结果列表的头部。
  5. 返回结果列表。
import java.util.*;

public class Solution14 {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        //创建一个List<List<Integer>>类型的变量result,用于存储最终的结果
        List<List<Integer>> result = new ArrayList<>();
        //如果输入的root为空,则直接返回空的结果
        if (root == null) {
            return result;
        }

        //创建一个Queue<TreeNode>类型的变量queue,用于存储待遍历的节点。将初始的root节点加入到队列中
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        //当队列不为空时,进入循环。获取当前队列的大小,并创建一个List<Integer>类型的变量level,用于存储当前层次的节点值
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<>();

            //遍历当前层次的节点。从队列中取出一个节点,并将其值加入到level中
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);

                //如果当前节点的左子节点不为空,则将其加入到队列中。如果当前节点的右子节点不为空,则将其加入到队列中
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            //将当前层次的节点值列表level加入到结果列表result的首部
            result.add(0, level);
        }

        return result;
    }

    public static void main(String[] args) {

    }
}

使用了一个队列来辅助进行广度优先搜索,通过不断出队节点并将其子节点入队,实现了自底向上的层序遍历。最后将每一层的节点值列表添加到结果列表的头部,即可得到自底向上的层序遍历结果。 

 十五、将有序数组转化为二叉搜索树

 

要将一个升序排列的整数数组转换为一棵高度平衡的二叉搜索树,可以使用递归的方法来实现。

首先,我们需要找到数组的中间元素,将其作为根节点。然后,将数组分为左右两个部分,分别递归构建左子树和右子树

  1. 定义一个递归函数 buildBST,该函数接收一个整数数组 nums 和两个整数 leftright,表示当前子数组的左右边界。
  2. 如果 left > right,说明当前子数组为空,返回 null
  3. 计算中间元素的索引 mid,可以选择中间位置的元素,也可以选择左边界和右边界的中间位置的元素。
  4. 创建一个新的节点,值为 nums[mid],作为当前子树的根节点。5. 递归调用 buildBST 构建左子树,传入参数 numsleftmid-1
  5. 递归调用 buildBST 构建右子树,传入参数 numsmid+1right
  6. 将左子树和右子树分别作为当前节点的左右子节点。
  7. 返回当前节点。

 

public class Solution15 {
    //它接受一个有序数组作为参数,并返回一个二叉搜索树的根节点
    public TreeNode sortedArrayToBST(int[] nums) {
        //调用了私有方法buildBST来构建二叉搜索树
        return buildBST(nums, 0, nums.length - 1);
    }

    //接受一个有序数组、左边界和右边界作为参数,并返回一个二叉搜索树的根节点。它使用递归的方式构建二叉搜索树
    private TreeNode buildBST(int[] nums, int left, int right) {
        //检查左边界是否大于右边界,如果是,则返回null,表示当前子树为空
        if (left > right) {
            return null;
        }

        //计算中间元素的索引,将其作为根节点的值创建一个新的节点
        int mid = left + (right - left) / 2;
        //递归地调用buildBST方法来构建左子树和右子树
        TreeNode root = new TreeNode(nums[mid]);
        //左子树的范围是从左边界到中间元素的前一个元素
        root.left = buildBST(nums, left, mid - 1);
        //右子树的范围是从中间元素的后一个元素到右边界
        root.right = buildBST(nums, mid + 1, right);

        //将左子树和右子树分别赋值给根节点的左节点和右节点,并返回根节点
        return root;
    }

    public static void main(String[] args) {

    }
}

 调用 sortedArrayToBST 方法,传入一个升序排列的整数数组,即可得到一棵高度平衡的二叉搜索树。将有序数组转换为一个平衡的二叉搜索树。通过递归地将数组分成两半,并将中间元素作为根节点,可以保证生成的二叉搜索树是平衡的

 十六、有序链表转换二叉搜索树

要将一个升序排列的单链表转换为高度平衡的二叉搜索树,可以使用递归的方法来解决。

  1. 首先,找到链表的中间节点,作为二叉搜索树的根节点。可以使用快慢指针的方法来找到中间节点。快指针每次移动两步,慢指针每次移动一步,当快指针到达链表末尾时,慢指针指向的节点就是中间节点。

  2. 将链表分成两部分,左边部分作为左子树,右边部分作为右子树。

  3. 递归地构建左子树和右子树,分别将左子链表和右子链表传入递归函数。

  4. 将左子树和右子树连接到根节点上。

  5. 返回根节点。

 

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

public class Solution16 {
    public TreeNode sortedListToBST(ListNode head) {
        //如果链表为空,直接返回空
        if (head == null) {
            return null;
        }
        //如果链表只有一个节点,将该节点的值作为根节点创建一个新的二叉搜索树节点,并返回
        if (head.next == null) {
            return new TreeNode(head.val);
        }

        ListNode slow = head;//定义一个慢指针slow,初始指向链表头节点
        ListNode fast = head;//定义一个快指针fast,初始指向链表头节点
        ListNode prev = null;//定义一个指针prev,用于记录slow的前一个节点

        //使用快慢指针的方式找到链表的中间节点slow。快指针每次移动两步,慢指针每次移动一步
        // 当快指针到达链表末尾时,慢指针指向的节点就是中间节点
        while (fast != null && fast.next != null) {
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        //将链表断开,将slow的前一个节点的next指针置为null,将链表分为两部分
        prev.next = null;

        //以slow节点的值创建一个新的二叉搜索树节点作为根节点
        TreeNode root = new TreeNode(slow.val);
        //递归调用函数,将链表的前半部分作为参数传入,构建左子树
        root.left = sortedListToBST(head);
        //递归调用函数,将链表的后半部分作为参数传入,构建右子树
        root.right = sortedListToBST(slow.next);

        return root;
    }

    public static void main(String[] args) {

    }
}

使用了快慢指针的方法找到链表的中间节点,并递归地构建左子树和右子树。时间复杂度为O(nlogn),其中n是链表的长度。 将有序链表转换为平衡的二叉搜索树。通过快慢指针的方式找到链表的中间节点,将链表分为两部分,然后递归地构建左子树和右子树,最后返回根节点。

十七、平衡二叉树 

 

要判断一个二叉树是否是高度平衡的,可以使用递归的方法来解决。

首先,我们需要定义一个函数来计算二叉树的高度。

然后,对于每个节点,我们可以递归地计算其左子树和右子树的高度,并判断它们的高度差是否超过1。如果有任何一个节点的高度差超过1,那么这棵二叉树就不是高度平衡的。 

public class Solution17 {
    public boolean isBalanced(TreeNode root) {
        //如果二叉树为空,即root为null,则认为是平衡的,返回true
        if (root == null) {
            return true;
        }

        //计算左子树和右子树的高度
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);

        //判断它们的高度差是否大于1,如果高度差大于1,则认为不平衡,返回false
        if (Math.abs(leftHeight - rightHeight) > 1) {
            return false;
        }

        //如果高度差小于等于1,则递归地判断左子树和右子树是否平衡,如果都平衡,则整个二叉树是平衡的,返回true
        return isBalanced(root.left) && isBalanced(root.right);
    }

    private int getHeight(TreeNode node) {//计算给定节点的高度
        //如果节点为空,即node为null,则认为高度为0
        if (node == null) {
            return 0;
        }

        //计算左子树和右子树的高度
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);

        //取较大值加1作为当前节点的高度
        return Math.max(leftHeight, rightHeight) + 1;
    }

    public static void main(String[] args) {

    }
}

上述代码中,isBalanced函数用于判断二叉树是否是高度平衡的,getHeight函数用于计算二叉树的高度。我们首先判断根节点的左子树和右子树的高度差是否超过1,如果超过1,则返回false。然后递归地判断左子树和右子树是否是高度平衡的。

十八、二叉树的最小深度 

要找出二叉树的最小深度,可以使用递归的方式进行求解。

首先需要考虑几种特殊情况:

  1. 如果二叉树为空,则最小深度为0;
  2. 如果二叉树只有根节点,即没有左右子树,则最小深度为1;
  3. 如果二叉树只有左子树或者只有右子树,则最小深度为左子树或右子树的最小深度加1。

对于一般情况,可以通过递归的方式求解最小深度。具体步骤如下:

  1. 分别计算左子树和右子树的最小深度,即递归调用求解函数;
  2. 如果左子树或右子树为空,则返回非空子树的最小深度加1;
  3. 如果左子树和右子树都不为空,则返回左子树和右子树的最小深度的较小值加1。
public class Solution18 {
    public int minDepth(TreeNode root) {
        //如果传入的root为null,表示当前节点为空,直接返回0
        if (root == null) {
            return 0;
        }

        //如果当前节点的左子节点和右子节点都为空,表示当前节点为叶子节点,返回1
        if (root.left == null && root.right == null) {
            return 1;
        }

        //递归调用minDepth方法,分别计算左子树和右子树的最小深度,并将结果保存在leftDepth和rightDepth变量中
        int leftDepth = minDepth(root.left);
        int rightDepth = minDepth(root.right);

        //如果当前节点的左子节点或右子节点为空,表示当前节点只有一个子节点,返回左子树和右子树的最小深度加1
        if (root.left == null || root.right == null) {
            return leftDepth + rightDepth + 1;
        }

        //如果当前节点的左子节点和右子节点都不为空,返回左子树和右子树的最小深度的较小值加1
        return Math.min(leftDepth, rightDepth) + 1;
    }

    public static void main(String[] args) {

    }
}

十九、路径总和

要判断二叉树中是否存在根节点到叶子节点的路径,使得路径上所有节点值相加等于目标和targetSum,可以使用递归的方式进行深度优先搜索。

  1. 首先判断根节点是否为空,如果为空,则直接返回false。
  2. 然后判断当前节点是否为叶子节点,如果是叶子节点,则判断当前节点的值是否等于目标和targetSum,如果相等,则返回true,否则返回false。
  3. 如果当前节点不是叶子节点,则递归地判断其左子树和右子树是否存在满足条件的路径。递归的过程中,需要将目标和减去当前节点的值,作为新的目标和传递给子节点。
  4. 如果左子树或右子树中存在满足条件的路径,则返回true,否则返回false。

 

public class Solution19 {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // 如果根节点为空,返回false
        if (root == null) {
            return false;
        }
        // 如果根节点的左子节点和右子节点都为空,判断根节点的值是否等于目标和
        if (root.left == null && root.right == null) {
            return root.val == targetSum;
        }
        // 递归调用hasPathSum方法,传入左子节点和目标和减去根节点的值,或者右子节点和目标和减去根节点的值
        return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }

    public static void main(String[] args) {

    }
}

功能是判断给定的二叉树中是否存在一条从根节点到叶子节点的路径,使得路径上所有节点的值之和等于目标和targetSum。如果存在这样的路径,则返回true,否则返回false。 

二十、路经总和Ⅱ 

要找出从根节点到叶子节点路径总和等于给定目标和的路径,可以使用深度优先搜索(DFS)的方法来解决这个问题。

  1. 定义一个结果集,用于存储所有满足条件的路径。
  2. 定义一个临时路径,用于存储当前遍历的路径。
  3. 从根节点开始进行深度优先搜索。
  4. 在每个节点上,将当前节点的值添加到临时路径中,并将目标和减去当前节点的值。
  5. 如果当前节点是叶子节点,并且目标和等于0,则将临时路径添加到结果集中。
  6. 递归地遍历当前节点的左子树和右子树。
  7. 在递归返回之前,需要将临时路径中的当前节点移除,以便继续遍历其他路径。
import java.util.ArrayList;
import java.util.List;

public class Solution20 {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        // 创建一个用于存储结果的列表
        List<List<Integer>> result = new ArrayList<>();
        // 创建一个用于存储当前路径的列表
        List<Integer> path = new ArrayList<>();
        // 调用递归函数进行深度优先搜索
        dfs(root, targetSum, path, result);
        // 返回结果列表
        return result;
    }

    private void dfs(TreeNode node, int targetSum, List<Integer> path, List<List<Integer>> result) {
        // 如果当前节点为空,直接返回
        if (node == null) {
            return;
        }
        // 将当前节点的值添加到路径列表中
        path.add(node.val);
        // 更新目标和
        targetSum -= node.val;
        // 如果当前节点是叶子节点且目标和为0,将当前路径添加到结果列表中
        if (node.left == null && node.right == null && targetSum == 0) {
            result.add(new ArrayList<>(path));
        }
        // 递归遍历左子树
        dfs(node.left, targetSum, path, result);
        // 递归遍历右子树
        dfs(node.right, targetSum, path, result);
        // 回溯,将当前节点从路径列表中移除
        path.remove(path.size() - 1);
    }

    public static void main(String[] args) {
        
    }
}

源码链接:https://pan.baidu.com/s/1Klvr-CrtSoGpXXv1f3dZbw 
提取码:xxyq

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值