数据结构与算法 - 二叉树

news2024/10/2 10:34:27

1. 概述

二叉树是这么一种树状结构:每个节点最多有两个孩子,左孩子和右孩子

完全二叉树:是一种二叉树结构,除了最后一层以外,每一层都必须填满,填充时要遵循从左到右

平衡二叉树:是一种二叉树结构,其中每个节点的左右子树高度相差不超过1

2. 存储

存储方式分为两种:

①定义树结点与左、右孩子引用(TreeNode)

②使用数组,若用0作为树的根节点,索引可以通过以下方式计算

  • 父 = floor((子 - 1) / 2)
  • 左孩子 = 父 * 2 + 1
  • 右孩子 = 父 * 2 + 2

3. 遍历

遍历方式也分为两种:

①广度优先遍历:尽可能先访问距离根节点最近的节点,也称为层序遍历

②深度优先遍历:对于二叉树,可以进一步划分为三种(要深入到叶子节点)

  • pre-order前序遍历:对于每一棵子树,先访问该节点,然后是左子树,最后是右子树
  • in-order中序遍历:对于每一棵子树,先访问左子树,然后是该节点,最后是右子树
  • post-order后续遍历:对于每一棵子树,先访问左子树,然后是右子树,最后是该节点

3.1 广度优先遍历

本轮开始时队列本轮访问节点
[1]1
[2, 3]2
[3, 4]3
[4, 5, 6]4
[5, 6]5
[6, 7, 8]6
[7, 8]7
[8]8
[]

1. 初始化,将根节点加入队列

2. 循环处理队列中每个节点,直至队列为空

3. 每次循环内处理节点后,将它的孩子节点(即下一层节点)加入队列

注意:

  • 以上用队列来实现层序遍历是针对TreeNode这种方式表示的二叉树
  • 对于数组实现的二叉树,则直接遍历数组即可,自然为层序遍历的顺序

3.2 深度优先遍历

栈暂存已处理前序遍历中序遍历
[1]1 ✔️ 左💤 右💤1
[1, 2]2✔️ 左💤 右💤 1✔️ 左💤 右💤2
[1, 2, 4]4✔️ 左✔️ 右✔️ 2✔️ 左💤 右💤 1✔️ 左💤 右💤44
[1, 2]2✔️ 左✔️ 右✔️ 1✔️ 左💤 右💤2
[1]1✔️ 左✔️ 右💤1
[1, 3]3✔️ 左💤 右💤 1✔️ 左✔️ 右💤3
[1, 3, 5]5✔️ 左✔️ 右✔️ 3✔️ 左💤 右💤 1✔️ 左✔️ 右💤55
[1, 3]3✔️ 左✔️ 右💤 1✔️ 左✔️ 右💤3
[1, 3, 6]6✔️ 左✔️ 右✔️ 3✔️ 左✔️ 右💤 1✔️ 左✔️ 右💤66
[1, 3]3✔️ 左✔️ 右✔️ 1✔️ 左✔️ 右💤
[1]1✔️ 左✔️ 右✔️
[]

3.2.1 递归实现

/**
 * <h3>前序遍历</h3>
 * @param node 节点
 */
static void preOrder(TreeNode node) {
    if (node == null) {
        return;
    }
    System.out.print(node.val + "\t"); // 值
    preOrder(node.left); // 左
    preOrder(node.right); // 右
}

/**
 * <h3>中序遍历</h3>
 * @param node 节点
 */
static void inOrder(TreeNode node) {
    if (node == null) {
        return;
    }
    inOrder(node.left); // 左
    System.out.print(node.val + "\t"); // 值
    inOrder(node.right); // 右
}

/**
 * <h3>后序遍历</h3>
 * @param node 节点
 */
static void postOrder(TreeNode node) {
    if (node == null) {
        return;
    }
    postOrder(node.left); // 左
    postOrder(node.right); // 右
    System.out.print(node.val + "\t"); // 值
}

3.2.2 非递归实现

前序遍历

LinkedListStack<TreeNode> stack = new LinkedListStack<>();  // 此处的LinkedListStack为自己实现的
TreeNode curr = root;

while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        System.out.println(curr);
        curr = curr.left;
    } else {
        TreeNode pop = stack.pop();
        curr = pop.right;
    }

}

中序遍历

LinkedListStack<TreeNode> stack = new LinkedListStack<>();
TreeNode curr = root;

while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        curr = curr.left;
    } else {
        TreeNode pop = stack.pop();
        System.out.println(pop);
        curr = pop.right;
    }
}

后序遍历

LinkedListStack<TreeNode> stack = new LinkedListStack<>();
TreeNode curr = root;
TreeNode pop = null;

while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        curr = curr.left;
    } else {
        TreeNode peek = stack.peek();
        if (peek.right == null || peek.right == pop) {
            pop = stack.pop();
            System.out.println(pop);
        } else {
            curr = peek.right;
        }
    }
}

对于后序遍历,向后走时,需要处理完右子树才能pop出栈。如何直到右子树处理完成呢?

①如果栈顶元素的 right == null ,表示没啥可处理的,可以出栈

②如果栈顶元素的 right != null 

  • 那么使用lastPop记录最近出栈的节点,即表示从这个节点向回走
  • 如果栈顶元素 right == lastPop,此时应当出栈

对于前、中两种遍历,实际以上代码从右子树向回走时,并未走完全程(stack提前出栈了),而后序遍历以上代码是走完全程了。

统一写法(依据后序遍历修改)

LinkedList<TreeNode> stack = new LinkedList<>();

TreeNode curr = root; // 代表当前节点
TreeNode pop = null; // 最近一次弹栈的元素
while (curr != null || !stack.isEmpty()) {
    if (curr != null) {
        colorPrintln("前: " + curr.val, 31);
        stack.push(curr); // 压入栈,为了记住回来的路
        curr = curr.left;
    } else {
        TreeNode peek = stack.peek();
        // 右子树可以不处理, 对中序来说, 要在右子树处理之前打印
        if (peek.right == null) {
            colorPrintln("中: " + peek.val, 36);
            pop = stack.pop();
            colorPrintln("后: " + pop.val, 34);
        }
        // 右子树处理完成, 对中序来说, 无需打印
        else if (peek.right == pop) {
            pop = stack.pop();
            colorPrintln("后: " + pop.val, 34);
        }
        // 右子树待处理, 对中序来说, 要在右子树处理之前打印
        else {
            colorPrintln("中: " + peek.val, 36);
            curr = peek.right;
        }
    }
}

public static void colorPrintln(String origin, int color) {
    System.out.printf("\033[%dm%s\033[0m%n", color, origin);
}

一张图演示三种遍历

  • 红色:前序遍历
  • 绿色:中序遍历
  • 蓝色:后序遍历

4. 习题

4.1 前序遍历二叉树

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

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

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:

输入:root = [1,2]
输出:[1,2]

示例 5:

输入:root = [1,null,2]
输出:[1,2]

提示:

  • 树中节点数目在范围 [0, 100] 内
  • -100 <= Node.val <= 100

进阶:递归算法很简单,你可以通过迭代算法完成吗?

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preorderHelper(root, result);
        return result;
    }

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

解法二:迭代

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        LinkedList<TreeNode> stack = new LinkedList<>();
        List<Integer> result = new ArrayList<>();
        TreeNode curr = root;

        while (!stack.isEmpty() || curr != null) {
            if (curr != null) {
                stack.push(curr);
                result.add(curr.val);
                curr = curr.left;
            } else {
                TreeNode pop = stack.pop();
                curr = pop.right;
            }
        }

        return result;
    }
}

解法三:莫里斯遍历(Morris Traversal)

①莫里斯遍历的核心思想是通过利用树的空指针链接来避免使用栈

②对于每个节点,如果它的左子树为空,则访问当前节点并移动到右子树

③如果左子树不为空,找到当前节点的前驱节点(即左子树中最右的节点),检查它的右指针

  • 如果它的右指针为空,则将其指向当前节点,并返回当前节点
  • 如果它的右指针已经指向当前节点,说明左子树已经遍历结束,将右指针恢复为null,并移动到右子树。

时间复杂度:O(n);空间复杂度:O(1)

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {  
    public List<Integer> preorderTraversal(TreeNode root) {  
        List<Integer> result = new ArrayList<>();  
        TreeNode curr = root;  

        while (curr != null) {  
            if (curr.left == null) {  
                // 访问当前节点  
                result.add(curr.val);  
                curr = curr.right; // 移动到右子树  
            } else {  
                // 找到当前节点的前驱节点  
                TreeNode pred = curr.left;  
                while (pred.right != null && pred.right != curr) {  
                    pred = pred.right;  
                }  
                
                // 建立链接  
                if (pred.right == null) {  
                    pred.right = curr; // 建立临时连接  
                    result.add(curr.val); // 访问当前节点  
                    curr = curr.left; // 移动到左子树  
                } else {  
                    // 恢复树结构  
                    pred.right = null;  
                    curr = curr.right; // 移动到右子树  
                }  
            }  
        }  

        return result;  
    }  
}  

4.2 中序遍历二叉树

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

示例 1:

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

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点数目在范围 [0, 100] 内
  • -100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorderHelper(root, result);
        return result;
    }

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

解法二:迭代

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        LinkedList<TreeNode> stack = new LinkedList<>();
        List<Integer> result = new ArrayList<>();
        TreeNode curr = root;

        while (!stack.isEmpty() || curr != null) {
            if (curr != null) {
                stack.push(curr);
                curr = curr.left;
            } else {
                TreeNode pop = stack.pop();
                result.add(pop.val);
                curr = pop.right;
            }
        }

        return result;
    }
}

解法三:莫里斯算法

①莫里斯遍历的核心思想是通过利用树的空指针链接来避免使用栈

②对于每个节点,如果它的左子树为空,则访问当前节点并移动到右子树

③如果左子树不为空,找到当前节点的前驱节点(即左子树中最右的节点),检查它的右指针

  • 如果它的右指针为空,则将其指向当前节点,并返回当前节点
  • 如果它的右指针已经指向当前节点,说明左子树已经遍历结束,将右指针恢复为null,并移动到右子树。
/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        TreeNode curr = root;

        while (curr != null) {
            if (curr.left == null) { // 左子树为空
                // 访问当前节点
                result.add(curr.val);
                // 移动到右子树
                curr = curr.right;
            } else {
                // 找到当前节点的前驱节点
                TreeNode pred = curr.left;
                while (pred.right != null && pred.right != curr) {
                    pred = pred.right;
                }

                // 建立链接
                if (pred.right == null) {
                    // 建立临时链接
                    pred.right = curr;
                    // 移动到左子树
                    curr = curr.left;
                } else {
                    // 恢复树结构
                    pred.right = null;
                    // 访问当前节点
                    result.add(curr.val);
                    // 移动到右子树
                    curr = curr.right;
                }
            }
        }
        return result;
    }
}

4.3 后序遍历二叉树

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 

示例 1:

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

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点的数目在范围 [0, 100] 内
  • -100 <= Node.val <= 100

进阶:递归算法很简单,你可以通过迭代算法完成吗?

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postOrderHelper(root, result);
        return result;
    }

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

解法二:迭代

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        LinkedList<TreeNode> stack = new LinkedList<>();
        List<Integer> result = new ArrayList<>();
        TreeNode curr = root;
        TreeNode pop = null;

        while (!stack.isEmpty() || curr != null) {
            if (curr != null) {
                stack.push(curr);
                curr = curr.left;
            } else {
                TreeNode peek = stack.peek();
                if (peek.right == null || peek.right == pop) {
                    pop = stack.pop();
                    result.add(pop.val);
                } else {
                    curr = peek.right;
                }
            }
        }

        return result;
    }
}

4.4 对称二叉树

给你一个二叉树的根节点 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

进阶:你可以运用递归和迭代两种方法解决这个问题吗?

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) {
            return true;
        }
        return dfs(root.left, root.right);
    }

    private boolean dfs(TreeNode left, TreeNode right) {
        if(left == null && right == null) {
            return true;
        } 
        if(left == null || right == null) {
            return false;
        }

        return (left.val == right.val) && dfs(left.left, right.right) && dfs(left.right, right.left);
    }
}

解法二:迭代

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }

        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) {  // 左右两个子树为空
                continue;
            }

            if (leftNode == null || rightNode == null) {  // 两边只有一个子树为空
                return false;
            }

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

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

        return true;
    }
}

4.5 二叉树最大深度

给定一个二叉树 root ,返回其最大深度。

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

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:3

示例 2:

输入:root = [1,null,2]
输出:2

提示:

  • 树中节点的数量在 [0, 10^4] 区间内。
  • -100 <= Node.val <= 100

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

解法二:

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Stack<Pair<TreeNode, Integer>> stack = new Stack<>();
        stack.push(new Pair<>(root, 1));
        int maxDepth = 0;

        while (!stack.isEmpty()) {
            Pair<TreeNode, Integer> current = stack.pop();
            TreeNode node = current.getKey();
            int depth = current.getValue();
            maxDepth = Math.max(depth, maxDepth);

            if (node.left != null) {
                stack.push(new Pair<>(node.left, depth + 1));
            }
            if (node.right != null) {
                stack.push(new Pair<>(node.right, depth + 1));
            }
        }

        return maxDepth;
    }
}

解法三:使用二叉树的非递归后序遍历,栈的最大高度即为最大深度

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    // 使用非递归后序遍历,栈的最大高度即为最大深度
    public int maxDepth(TreeNode root) {
        TreeNode curr = root;
        TreeNode pop = null;
        LinkedList<TreeNode> stack = new LinkedList<>();
        int max = 0; // 栈的最大高度

        while(curr != null || !stack.isEmpty()) {
            if(curr != null) {
                stack.push(curr);
                max = Integer.max(stack.size(), max);
                curr = curr.left;
            } else {
                TreeNode peek = stack.peek();
                if(peek.right == null || peek.right == pop) {
                    pop = stack.pop();
                } else {
                    curr = peek.right;
                }
            }
        }
        return max;
    }
}

解法四:二叉树的层序遍历,层数即最大深度

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (poll.left != null) {
                    queue.offer(poll.left);
                }
                if (poll.right != null) {
                    queue.offer(poll.right);
                }
            }
            depth++;
        }

        return depth;
    }
}

4.6 二叉树最小深度

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

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

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

示例 1:

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

示例 2:

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

提示:

  • 树中节点数的范围在 [0, 10^5] 内
  • -1000 <= Node.val <= 1000

解法一:层序遍历。遇到第一个叶子节点所在层数即为最小深度

/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 1;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                if (node.left == null && node.right == null) {
                    return depth;
                }
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            depth++;
        }
        return depth;
    }
}

解法二:后序遍历

相较于求最大深度,应当考虑:

  • 当右子树为null,应当返回左子树深度加一
  • 当左子树为null,应当返回右子树深度加一
/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public int minDepth(TreeNode node) {
        if (node == null) {
            return 0;
        }
        int d1 = minDepth(node.left);
        int d2 = minDepth(node.right);
        if (d1 == 0 || d2 == 0) {
            return d1 + d2 + 1;
        }
        return Integer.min(d1, d2) + 1;
    }
}

4.7 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

示例 2:

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

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目范围在 [0, 100] 内
  • -100 <= Node.val <= 100

解法一:递归

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        reverse(root);
        return root;
    }

    private void reverse(TreeNode node) {
        if(node == null) {
            return;
        }

        TreeNode t = node.left;
        node.left = node.right;
        node.right = t;

        reverse(node.left);
        reverse(node.right);
    }
}

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null) {
            return null;
        }

        // 递归翻转左右子树
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);

        // 交换左右子树
        // TreeNode t = root.left;
        root.left = root.right;
        root.right = left;

        return root;
    }
}

4.8 后缀表达式转二叉树

  • 遇到运算符,则出栈两次,将出栈元素与当前节点建立父子关系,当前节点入栈
  • 遇到数字则入栈
    public TreeNode constructExpressionTree(String[] tokens) {
        LinkedList<TreeNode> stack = new LinkedList<>();
        for (String token : tokens) {
            switch (token) {
                // 遇到运算符,出栈两次,与当前节点建立父子关系,将当前节点入栈
                case "+", "-", "*", "/" -> {
                    TreeNode right = stack.pop();
                    TreeNode left = stack.pop();
                    TreeNode parent = new TreeNode(Integer.parseInt(token));
                    parent.left = left;
                    parent.right = right;
                    stack.push(parent);
                }
                default -> {  // 遇到数字入栈
                    stack.push(new TreeNode(Integer.parseInt(token)));
                }
            }
        }
        return stack.peek();
    }

4.9 根据前序与中序遍历结果构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

解法一:

  • 先通过前序遍历结果定位根节点
  • 再结合中序遍历结果切分左右子树
/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            indexMap.put(inorder[i], i);
        }

        return buildTreeHelper(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1, indexMap);
    }

    private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int preStart, int preEnd, int inStart, int inEnd,
            Map<Integer, Integer> indexMap) {
        if (preStart > preEnd || inStart > inEnd) {
            return null;
        }

        int rootVal = preorder[preStart]; // 根节点的位置
        TreeNode root = new TreeNode(rootVal);

        int inIndex = indexMap.get(rootVal);
        int leftSubtreeSize = inIndex - inStart; // 左子树

        root.left = buildTreeHelper(preorder, inorder, preStart + 1, preStart + leftSubtreeSize, inStart, inIndex - 1,
                indexMap);
        root.right = buildTreeHelper(preorder, inorder, preStart + leftSubtreeSize + 1, preEnd, inIndex + 1, inEnd,
                indexMap);

        return root;
    }
}

4.10 根据中序与后序遍历结果构造二叉树

解法一:

  • 先通过后序遍历结果定位根节点
  • 再结合中序遍历结果划分左右子树
/**
 * Definition for a binary tree node.
 * 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;
 * }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            indexMap.put(inorder[i], i);
        }

        return buildTreeHelper(inorder, postorder, 0, inorder.length - 1, 0, postorder.length - 1, indexMap);
    }

    private TreeNode buildTreeHelper(int[] inorder, int[] postorder, int inStart, int inEnd, int postStart, int postEnd,
            Map<Integer, Integer> indexMap) {
        if (inStart > inEnd || postStart > postEnd) {
            return null;
        }

        int rootVal = postorder[postEnd]; // 根节点的位置
        TreeNode root = new TreeNode(rootVal);

        int inIndex = indexMap.get(rootVal);
        int rightSubtreeSize = inEnd - inIndex; // 右子树

        root.left = buildTreeHelper(inorder, postorder, inStart, inIndex - 1, postStart, postEnd - rightSubtreeSize - 1,
                indexMap);
        root.right = buildTreeHelper(inorder, postorder, inIndex + 1, inEnd, postEnd - rightSubtreeSize, postEnd - 1,
                indexMap);

        return root;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基础算法之模拟

1P1093 [NOIP2007 普及组] 奖学金 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1093https://www.luogu.com.cn/problem/P1093 #include<iostream> #include<algorithm> using namespace std; struct stu {int num;//编号int c…

尚品汇-首页三级分类实现-nginx静态代理生成的静态页面(二十六)

目录&#xff1a; &#xff08;1&#xff09;问题详解 &#xff08;2&#xff09;首页商品分类实现 &#xff08;3&#xff09;修改web-all模块 &#xff08;4&#xff09;页面渲染 &#xff08;1&#xff09;问题详解 &#xff08;2&#xff09;首页商品分类实现 前面做了…

【书生大模型实战营(暑假场)】入门任务三 Python 关卡

入门任务二 Python 关卡 参考&#xff1a; 教程任务 1 闯关任务 1.1 使用 Python 实现 wordcount import stringdef wordcount(text):# 去除标点符号text text.translate(str.maketrans(, , string.punctuation))# 转换为小写text text.lower()# 分割字符串成单词列表wo…

CH571F蓝牙orUSB摇杆鼠标

演示视频&#xff1a; 短视频刷个爽 程序基本上是基于官方的例程上改的&#xff0c;用到的例程有&#xff1a;蓝牙的HID_Mouse,USB的CompoundDev&#xff0c;还有ADC&#xff0c;按键中断。 主要原理 就是ADC采集采集摇杆电压&#xff0c;通过蓝牙HID或者USB的HID发送给电脑或…

文心一言 VS 讯飞星火 VS chatgpt (317)-- 算法导论22.3 9题

九、请给出如下猜想的一个反例&#xff1a;如果有向图G包含一条从结点u到结点v的路径&#xff0c;则任何对图G的深度优先搜索都将导致v.d⩽u.f。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 首先&#xff0c;我们需要澄清问题中的几个关键点。在图的深度优先…

想做抖音短视频,视频素材去哪里找啊?

各位抖音上的短视频创作者们&#xff0c;是否曾幻想过自己的作品能够在全网爆火&#xff0c;却常因为缺少那些能够让视频更加生动的素材而感到困扰&#xff1f;不用担心&#xff0c;今天我要为大家介绍几个优秀的视频素材网站&#xff0c;让你的抖音之路顺风顺水&#xff01; …

Linux系统中的高级用户空间与内核空间交互技术

Linux作为一种开源操作系统&#xff0c;具有良好的稳定性、安全性和自定制性&#xff0c;因而在各种设备和场景中得到广泛应用。作为Linux系统的核心组成部分&#xff0c;内核空间与用户空间交互技术对系统性能和功能扩展起着关键作用。本文将深入探讨Linux系统中的高级用户空间…

Vue Vine:带给你全新的 Vue 书写体验!

你好&#xff0c;我是 Kagol&#xff0c;个人公众号&#xff1a;前端开源星球。 上个月和 TinyVue 的小伙伴们一起参加了 VueConf 24 大会&#xff0c;有幸认识沈青川大佬&#xff0c;并了解了他的 Vue Vine 项目&#xff0c;Vue Vine 让你可以在一个文件中通过函数方式定义多…

系统化学习 H264视频编码(05)码流数据及相关概念解读

说明&#xff1a;我们参考黄金圈学习法&#xff08;什么是黄金圈法则?->模型 黄金圈法则&#xff0c;本文使用&#xff1a;why-what&#xff09;来学习音H264视频编码。本系列文章侧重于理解视频编码的知识体系和实践方法&#xff0c;理论方面会更多地讲清楚 音视频中概念的…

Nginx进阶-常见配置(二)

一、nginx 日志配置 nginx 日志介绍 nginx 有一个非常灵活的日志记录模式,每个级别的配置可以有各自独立的访问日志, 所需日志模块 ngx_http_log_module 的支持&#xff0c;日志格式通过 log_format 命令来定义&#xff0c;日志对于统计和排错是非常有利的&#xff0c;下面总…

【TwinCAT3教程】TwinCAT3 PLC 简单程序编写与调试

一、PLC 简单程序编写 1.1 新建TwinCAT3项目 (1)打开 TwinCAT 3,点击 New TwinCAT Project 新建 TC3 项目。 (2)选择 TwinCAT Project,输入项目名称和项目保存路径,然后点击确定。 1.2 添加PLC项目 1.2.1 步骤 (1)在树形资源管理器右键点击 PLC,选择 添加新项 新…

STM32F28335实验:继电器

继电器控制电机&#xff1a; 5s启动 5s停止 循环 管脚图&#xff1a; 管脚用的是GPIO15 驱动&#xff1a; beep.c /** leds.c** Created on: 2024年8月2日* Author: Administrator*/#include<relay.h>/***************************************************…

【算法设计题】查找给定结点的双亲结点(二叉树),第3题(C/C++)

目录 第3题 查找给定结点的双亲结点&#xff08;二叉树&#xff09; 得分点&#xff08;必背&#xff09; 题解 定义函数和初始化变量&#xff1a; 处理特殊情况&#xff1a; 遍历树&#xff1a; 中序遍历左子树&#xff1a; 处理右子树&#xff1a; 返回结果&#x…

LSTM实战之预测股票

&#x1f4c8; 用PyTorch搭建LSTM模型&#xff0c;轻松预测股票价格&#xff01;&#x1f680; Hey小伙伴们&#xff0c;今天给大家带来一个超级实用的项目教程——如何用PyTorch和LSTM模型来预测股票价格&#xff01;&#x1f31f; &#x1f50d; 项目背景 我们都知道股市是…

《Unity3D网络游戏实战》学习与实践--制作一款大乱斗游戏

角色类 基类Base Human是基础的角色类&#xff0c;它处理“操控角色”和“同步角色”的一些共有功能&#xff1b;CtrlHuman类代表“操控角色”​&#xff0c;它在BaseHuman类的基础上处理鼠标操控功能&#xff1b;SyncHuman类是“同步角色”类&#xff0c;它也继承自BaseHuman&…

MySQL的数据结构B+tree以及SQL优化

首先呢&#xff0c;我们知道MySQL的数据结构为Btree,那么其结构究竟是什么样的&#xff0c;为什么选择Btree&#xff0c;而不选择Btree。下面我们从其结构分析 1.Btree平衡多路查找树 B-tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录…

入门mem0.NET

入门mem0.NET 安装包 如果你的项目使用了EntityFrameworkCore,那么你可以跟随这个教程走 <ItemGroup><PackageReference Include"mem0.NET" Version"0.1.7" /><PackageReference Include"mem0.NET.Qdrant" Version"0.1.7…

云动态摘要 2024-08-04

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起&#xff01; [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造&…

java之IO篇——File、字节流、字符流

前言 IO流是用于读写文件中的数据&#xff0c;要读写文件之前可以创建文件获取文件对象再创建IO流&#xff0c;正文会先介绍File类&#xff0c;通过File类的构造方法获取文件的对象&#xff0c;创建文件或目录以及File类的一些方法获取文件对象的属性。后面还介绍了相关的IO流体…

Radxa ROCK 3C开发板编译Opencv,支持调用树莓派摄像头模块V2

目录 1、ROCK 3C和树莓派摄像头模块V2介绍2、ROCK 3C在rsetup开启支持3、测试指令4、编译Opencv4.1 增加swap&#xff0c;确保内存够用4.2 安装依赖和下载opencv4.3 编译参考链接 5、使用opencv调用树莓派摄像头模块V2 1、ROCK 3C和树莓派摄像头模块V2介绍 ROCK 3C 是一款基于…