树相关例题

树例题

Posted by Link on November 15, 2022

94.中序遍历

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

解答

递归法

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        doInorderTraversal(root, result);
        return result;
    }

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

辅助栈迭代法

    public List<Integer> inorderTraversal(TreeNode root) {
        if(null == root){
            return Collections.emptyList();
        }
        List<Integer> results = new ArrayList<>();
        Stack<TreeNode> nodes = new Stack<>();
        nodes.add(root);
        if(null != root.left) {
            injectLeftNode(root.left, nodes);
        }
        while (!nodes.isEmpty()) {
            TreeNode node = nodes.pop();
            results.add(node.val);
            if (null != node.right) {
                injectLeftNode(node.right, nodes);
            }
        }
        return results;
    }

    private void injectLeftNode(TreeNode node, Stack<TreeNode> nodes) {
        while (node != null) {
            nodes.add(node);
            node = node.left;
        }
    }

前序遍历

递归法

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        doPreorderTraversal(root, result);
        return result;
    }

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

辅助栈迭代法

    public List<Integer> preorderTraversal(TreeNode root) {
        if (null == root) {
            return null;
        }
        List<Integer> results = new ArrayList<>();
        Stack<TreeNode> nodeStack = new Stack<>();
        nodeStack.push(root);
        while (!nodeStack.isEmpty()) {
            TreeNode cur = nodeStack.pop();
            results.add(cur.val);
            if(null != cur.right){
                nodeStack.push(cur.right);
            }
            if(null != cur.left){
                nodeStack.push(cur.left);
            }
        }
        return results;
    }

后序遍历

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        doPostorderTraversal(root, result);
        return result;
    }

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

辅助栈迭代法,根右左先序的reverse

    public List<Integer> postorderTraversal(TreeNode root) {
        if (null == root) {
            return null;
        }
        List<Integer> results = new ArrayList<>();
        Stack<TreeNode> nodeStack = new Stack<>();
        nodeStack.push(root);
        while (!nodeStack.isEmpty()) {
            TreeNode cur = nodeStack.pop();
            results.add(cur.val);
            if(null != cur.left){
                nodeStack.push(cur.left);
            }
            if(null != cur.right){
                nodeStack.push(cur.right);
            }
        }
        Collections.reverse(results);
        return results;
    }

102.二叉树的层序遍历

题目详情

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

题目解答

两个队列的方式

    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> results = new ArrayList<>();
        if(null == root){
            return results;
        }
        Queue<TreeNode> cur = new LinkedList<>();
        cur.add(root);
        Queue<TreeNode> next = new LinkedList<>();
        List<Integer> curResult = new ArrayList<>();
        while (!cur.isEmpty()) {
            TreeNode node = cur.poll();
            curResult.add(node.val);
            if(null != node.left){
                next.add(node.left);
            }
            if(null != node.right){
                next.add(node.right);
            }

            if(cur.isEmpty()){
                cur = next;
                next = new LinkedList<>();
                results.add(curResult);
                curResult = new ArrayList<>();
            }
        }
        return results;
    }

按层计数的方式,少一个队列

    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> results = new ArrayList<>();
        if(null == root){
            return results;
        }
        Queue<TreeNode> cur = new LinkedList<>();
        cur.add(root);
        List<Integer> curResult = new ArrayList<>();
        while (!cur.isEmpty()) {
            int curSize = cur.size();
            for (int i = 0; i < curSize; i++) {
                TreeNode node = cur.poll();
                curResult.add(node.val);
                if(null != node.left){
                    cur.add(node.left);
                }
                if(null != node.right){
                    cur.add(node.right);
                }
            }
            results.add(curResult);
            curResult = new ArrayList<>();
        }
        return results;
    }

105. 从前序与中序遍历序列构造二叉树

题目详情

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

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

题目解答

递归,根节点3,使用根节点在中序找到3,然后中序分割左子树和右子数

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return doBuild(preorder, inorder, 0, 0, preorder.length);
    }

    private TreeNode doBuild(int[] preorder, int[] inorder, int preSt, int inSt, int length) {
        if(length == 0){
            return null;
        }

        TreeNode root = new TreeNode(preorder[preSt]);
        if(length == 1){
            return root;
        }

        //find root in left
        int i;
        for (i = 0; i < length; i++) {
            if (inorder[inSt + i] == root.val) {
                root.left = doBuild(preorder, inorder, preSt + 1, inSt, i);
                root.right = doBuild(preorder, inorder, preSt + i + 1, inSt + i + 1, length - i - 1);
                return root;
            }
        }
        return root;
    }

226.反转二叉树

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

题目解答

递归,非常简单且思路清晰 时间O(n) 空间O(h),如果是平衡二叉树则为O(logn)

    public TreeNode invertTree(TreeNode root) {
        if(null == root){
            return null;
        }

        TreeNode tmp = root.right;
        root.right = root.left;
        root.left = tmp;
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }

层序遍历时翻转(我觉得没啥意义) 时间O(n) 空间O(n)

101.对称二叉树

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

题目解答

思路简单清晰,两棵树对称 = 根节点想等,左子树和右子树对称,右子树与左子数对称

    public boolean isSymmetric(TreeNode root) {
        return isSymmetric(root, root);
    }

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

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

中序遍历

    public boolean isSymmetric(TreeNode root) {
        if(null == root){
            return true;
        }
        Stack<TreeNode> left = new Stack<>();
        Stack<TreeNode> right = new Stack<>();
        left.add(root);
        right.add(root);
        while(!left.isEmpty() && !right.isEmpty() && (left.size() == right.size())){
            TreeNode leftNode = left.pop();
            TreeNode rightNode = right.pop();
            if(leftNode == null && rightNode == null){
                continue;
            }
            if(leftNode == null || rightNode == null){
                return false;
            }
            if(leftNode.val != rightNode.val){
                return false;
            }
            injectLeft(leftNode.right, left);
            injectRight(rightNode.left, right);
        }

        return left.isEmpty() && right.isEmpty();
    }

    private void injectLeft(TreeNode node, Stack<TreeNode> left){
        while(node != null){
            left.add(node);
            node = node.left;
        }
    }

    private void injectRight(TreeNode node, Stack<TreeNode> right){
        while(node != null){
            right.add(node);
            node = node.right;
        }
    }

辅助队列方法,比较简单

    public boolean isSymmetric(TreeNode root) {
        return isSymmetric(root, root);
    }

    public boolean isSymmetric(TreeNode left, TreeNode right) {
        Queue<TreeNode> nodeQueue = new LinkedList<>();
        nodeQueue.offer(left);
        nodeQueue.offer(right);
        while (!nodeQueue.isEmpty()) {
            TreeNode node1 = nodeQueue.poll();
            TreeNode node2 = nodeQueue.poll();
            if(node1 == null && node2 == null){
                continue;
            }
            if(node1 == null || node2 == null || node1.val != node2.val){
                return false;
            }

            nodeQueue.offer(node1.left);
            nodeQueue.offer(node2.right);

            nodeQueue.offer(node1.right);
            nodeQueue.offer(node2.left);
        }
        return true;
    }

104.二叉树的最大深度

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

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

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

示例: 给定二叉树 [3,9,20,null,null,15,7],

3    / \   9  20
/  \    15   7 返回它的最大深度 3 。

题目解答

层序遍历(其实没必要)

    public int maxDepth(TreeNode root) {
        if (null == root) {
            return 0;
        }
        Queue<TreeNode> cur = new LinkedList<>();
        cur.add(root);
        int depth = 0;
        while (!cur.isEmpty()) {
            int curSize = cur.size();
            for (int i = 0; i < curSize; i++) {
                TreeNode node = cur.poll();
                if (null != node.left) {
                    cur.add(node.left);
                }
                if (null != node.right) {
                    cur.add(node.right);
                }
            }
            depth++;
        }
        return depth;
    }

递归BFS,简单直观

    public int maxDepth(TreeNode root) {
        if (null == root) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth((root.right))) + 1;
    }

二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 : 给定二叉树

      1
     / \
    2   3
   / \     
  4   5     返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

我自己的这个做法有点动态规划的味道,当前子数的最大值=左子树最大值,右子树最大值,左深度+右深度+1三者中最大的一个。

题目解答

    public int diameterOfBinaryTree(TreeNode root) {
        int[] depthAndNode = maxDepthAndPathNode(root);
        return depthAndNode[1] - 1;
    }

    private int[] maxDepthAndPathNode(TreeNode node){
        if(null == node){
            return new int[]{0, 0};
        }

        int[] left = maxDepthAndPathNode(node.left);
        int[] right = maxDepthAndPathNode(node.right);

        int pathNode = Math.max(left[0] + right[0] + 1, Math.max(left[1], right[1]));
        int maxDepth = Math.max(left[0], right[0]) + 1;
        return new int[]{maxDepth, pathNode};
    }

既然只是求最大值,其实迭代过程中计算即可

    private int max;
    public int diameterOfBinaryTree(TreeNode root) {
        max = 0;
        maxDepthAndPathNode(root);
        return max - 1;
    }

    private int maxDepthAndPathNode(TreeNode node){
        if(null == node){
            return 0;
        }

        int left = maxDepthAndPathNode(node.left);
        int right = maxDepthAndPathNode(node.right);

        int curNodePath = left + right + 1;
        max = Math.max(max, curNodePath);
        return Math.max(left, right) + 1;
    }

617. 合并二叉树

给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

返回合并后的二叉树。

    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1 == null && root2 == null){
            return null;
        }
        if(root1 == null){
            return root2;
        } else if(root2 == null){
            return root1;
        } else {
            root1.val = root1.val + root2.val;
            root1.left = mergeTrees(root1.left, root2.left);
            root1.right = mergeTrees(root1.right, root2.right);
            return root1;
        }
    }

96.不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

题目解答

动态规划,直接看了答案,不得不感叹动态规划最重要的还是无后效性的状态转移(比如到某个节点为止的xxx),其实程序算法基本上都是(比如排序后合并区间)。

  1. 确定的无后效性的状态转移即以i为根的二叉搜索树数 = 左子数二叉搜索树数 * 右子数二叉搜索树数
  2. f(i,n) 为以i为根的二叉搜索树数
  3. g(n) 为不同的二叉搜索树数
  4. f(i,n) = g(i-1) * g(n-i)
  5. g(n) = f(i,n)累加
  6. g(0) = 1
  7. g(1) = 1
    public int numTrees(int n) {
        int[] g = new int[n + 1];
        g[0] = 1;
        g[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                g[i] += g[j - 1] * g[i - j];
            }
        }
        return g[n];
    }

98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。

解答

递归方法,传递当前子树可以存在的最大值和最小值

    public boolean isValidBST(TreeNode root) {
        return validBSTAndMaxMin(root, Long.MAX_VALUE, Long.MIN_VALUE);
    }

    private boolean validBSTAndMaxMin(TreeNode node, Long max, Long min) {
        if (node == null) {
            return true;
        }
        if (node.val <= min || node.val >= max) {
            return false;
        }

        return validBSTAndMaxMin(node.left, (long) node.val, min)
                && validBSTAndMaxMin(node.right, max, (long) node.val);
    }

中序遍历,二叉搜索树的中序遍历是从小到大的

    public boolean isValidBST(TreeNode root) {
        if (null == root) {
            return true;
        }
        long last = Long.MIN_VALUE;
        Stack<TreeNode> nodeStack = new Stack<>();
        injectLeftNode(root, nodeStack);
        while (!nodeStack.isEmpty()) {
            TreeNode node = nodeStack.pop();
            if (last >= node.val) {
                return false;
            } else {
                last = node.val;
            }
            injectLeftNode(node.right, nodeStack);
        }
        return true;
    }

    private void injectLeftNode(TreeNode node, Stack<TreeNode> nodeStack) {
        while (node != null) {
            nodeStack.add(node);
            node = node.left;
        }
    }

2022-11-17

114.二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同。

解答

前序递归

    public void flatten(TreeNode root) {
        if (root == null || (null == root.left && null == root.right)) {
            return;
        }

        TreeNode left = root.left;
        TreeNode right = root.right;
        flatten(left);
        flatten(right);

        root.left = null;
        if (null != left) {
            root.right = left;
            while(left.right != null){
                left = left.right;
            }
            left.left = null;
            left.right = right;
        } else {
            root.right = right;
        }
    }

前序迭代

    public void flatten(TreeNode root) {
        if (null == root) {
            return;
        }
        Stack<TreeNode> nodeStack = new Stack<>();
        TreeNode pre = new TreeNode(-1);
        nodeStack.push(root);
        while (!nodeStack.isEmpty()) {
            TreeNode cur = nodeStack.pop();
            if(null != cur.right){
                nodeStack.push(cur.right);
            }
            if(null != cur.left){
                nodeStack.push(cur.left);
            }
            pre.left = null;
            pre.right = cur;
            pre = cur;
        }
    }

寻找前驱节点,左子树的最右节点是右子数根节点的前驱节点。根->左子树根->左子树最右节点->右子树根

    public void flatten(TreeNode root) {
        TreeNode cur = root;
        while (cur != null) {
            if (cur.left != null) {
                TreeNode next = cur.left;
                TreeNode preNode = next;

                //左子树最右节点
                while(preNode.right != null){
                    preNode = preNode.right;
                }
                preNode.right = cur.right;
                cur.left = null;
                cur.right = next;
            }
            cur = cur.right;
        }
    }

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。

解答

暴力回溯,查找出两个节点的所有祖先,然后比对。

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        List<TreeNode> pAncestors = ancestors(root, p);
        List<TreeNode> qAncestors = ancestors(root, q);
        for (TreeNode pAncestor : pAncestors) {
            for (TreeNode qAncestor : qAncestors) {
                if (pAncestor.val == qAncestor.val) {
                    return pAncestor;
                }
            }
        }
        return null;
    }

    private List<TreeNode> ancestors(TreeNode root, TreeNode p){
        List<TreeNode> ancestors = new ArrayList<>();
        addAncestors(root, p, ancestors);
        return ancestors;
    }

    private boolean addAncestors(TreeNode root, TreeNode p, List<TreeNode> ancestor){
        if(null == root){
            return false;
        }
        if(root.val == p.val){
            ancestor.add(root);
            return true;
        }
        if(addAncestors(root.left, p, ancestor) || addAncestors(root.right, p, ancestor)){
            ancestor.add(root);
            return true;
        }
        return false;
    }

巧妙的递归方法。DFS,这就是一个后序遍历的模型,只不过是每个父节点都会接收子节点的状态(是否含有p、q)并把这个状态往上传递,直到该结点满足祖先节点的条件。这样一想就豁然开朗了。

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        hasPOrQ(root, p, q);
        return result;
    }

    private boolean hasPOrQ(TreeNode root, TreeNode p, TreeNode q) {
        if (null == root) {
            return false;
        }
        boolean lSon = hasPOrQ(root.left, p, q);
        boolean rSon = hasPOrQ(root.right, p, q);

        //1. 左右子树都有p和q,那么p,q分布在两边,当前节点为公共祖先
        //2. 当前节点是p/q,左子树或右子树有p/q,pq一个为当前一个在任一子树
        if ((lSon && rSon) || ((root.val == p.val || root.val == q.val) && (lSon || rSon))) {
            //当首个公共祖先找到后,向上欺骗,该节点只包含一个,因此其他节点不会再被认为是祖先节点
            result = root;
        }
        
        //左子树有p/q或右子树有p/q或当前节点是p/q
        return lSon || rSon || root.val == p.val || root.val == q.val;
    }

437. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

题目解答

回溯算法,性能不好,其实遍历只用了O(n),但是有太多路径的更改增加操作,每个节点都需要循环路径

    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }
        doPath(root, targetSum, new LinkedList<>());
        return num;
    }

    //路径要用long,因为大int累加会溢出
    public void doPath(TreeNode node, int targetSum, LinkedList<Long> parentPaths) {
        if (null == node) {
            return;
        }
        if (node.val == targetSum) {
            num++;
        }

        for (int i = 0; i < parentPaths.size(); i++) {
            parentPaths.set(i, parentPaths.get(i) + node.val);
            if (parentPaths.get(i) == targetSum) {
                num++;
            }
        }
        //添加当前路径
        parentPaths.addLast((long) node.val);

        doPath(node.left, targetSum, parentPaths);
        doPath(node.right, targetSum, parentPaths);

        //回溯
        parentPaths.replaceAll(integer -> integer - node.val);
        parentPaths.removeLast();
    }

全部路径,重复遍历,路径总和 = 根节点开始的路径总和 + 左子树路径总和 + 右子树路径总和

    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }

        //每次递归都开出两条新的路径
        //从当前节点出发的路径
        int ret = rootSum(root, targetSum);
        //从当前左子树出发的路径
        ret += pathSum(root.left, targetSum);
        //从当前右子树出发的路径
        ret += pathSum(root.right, targetSum);
        return ret;
    }

    public int rootSum(TreeNode node, long targetSum) {
        if (null == node) {
            return 0;
        }
        int num = 0;
        if (node.val == targetSum) {
            num++;
        }

        int leftRet = rootSum(node.left, targetSum - node.val);
        int rightRet = rootSum(node.right, targetSum - node.val);
        return num + leftRet + rightRet;
    }

使用hash表对1方法进行改进,每次计算结果时,只需通过hash表查询,无需遍历路径

    public int pathSum(TreeNode root, int targetSum) {
        Map<Long, Integer> prefix = new HashMap<>();
        prefix.put(0L, 1);
        return dfs(root, prefix, 0, targetSum);
    }

    /**
     *
     * @param root 当前节点
     * @param prefix 根节点到目标路径
     * @param curr 根节点到目标节点的路径和
     * @param targetSum 目标路径和
     * @return
     */
    public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
        if (root == null) {
            return 0;
        }

        curr += root.val;

        int ret = prefix.getOrDefault(curr - targetSum, 0);
        prefix.put(curr, prefix.getOrDefault(curr, 0) + 1);
        ret += dfs(root.left, prefix, curr, targetSum);
        ret += dfs(root.right, prefix, curr, targetSum);

        //回溯
        prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);
        return ret;
    }

538. 把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。

题目解答

倒中序遍历 右中左

    public TreeNode convertBST(TreeNode root) {
        if (null == root || (null == root.left && null == root.right)) {
            return root;
        }
        int preSum = 0;
        Stack<TreeNode> nodeStack = new Stack<>();
        injectRightNode(root, nodeStack);
        while (!nodeStack.isEmpty()) {
            TreeNode node = nodeStack.pop();
            preSum += node.val;
            node.val = preSum;
            injectRightNode(node.left, nodeStack);
        }
        return root;
    }
    public TreeNode convertBST(TreeNode root) {
        doSum(root);
        return root;
    }

    private void doSum(TreeNode node){
        if(node == null){
            return;
        }
        doSum(node.right);
        preSum += node.val;
        node.val = preSum;
        doSum(node.left);
    }