二叉树

EmiyaCC 于 2021-06-21 发布

快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历

操作二叉树

关于二叉树的操作,无非是 BFS 和 DFS(应该),大体的框架也可以总结出来

// DFS
class Solution {
    public E function(E root) {
        if (root == null) return root;
        E left = function(root.left);
        E right = function(root.right);
        // 此处是一些操作,位置可以变,在后面就是后序遍历,其他位置同理
        return root;
    }
}
// BFS 不分层
class Solution {
    public E function(E root) {
        if (root == null) return root;
        Queue<E> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            E tmp = queue.poll();
            // 此处是一些操作
            if (tmp.left != null) queue.offer(tmp.left);
            if (tmp.right != null) queue.offer(tmp.right);
        }
        return root;
    }
}
// BFS 分层
class Solution {
    public E function(E root) {
        if (root == null) return root;
        Queue<E> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            // 对比不分层,此处多了一个计数
            int size = queue.size();
            for (int i = 0; i < size; i ++) {
                E tmp = queue.poll();
                // 此处是一些操作
            	if (tmp.left != null) queue.offer(tmp.left);
            	if (tmp.right != null) queue.offer(tmp.right);
            }
        }
        return root;
    } 
}

翻转二叉树

// https://leetcode-cn.com/problems/invert-binary-tree/
// 前序遍历 递归 DFS
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return root;
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}
// 迭代 BFS
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return root;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode tmp = queue.poll();
            TreeNode left = tmp.left;
            TreeNode right = tmp.right;
            tmp.left = right;
            tmp.right = left;
            if (left != null) queue.offer(left);
            if (right != null) queue.offer(right);
        }
        return root;
    }
}

填充每个节点的下一个右侧节点指针

对于一般的题来说(对于我),BFS 相对来说更好写出来,因为迭代更好理解一点,但是递归写出来就感觉,有大侠之姿(仅仅是帅,一直压栈也不支持)

// https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
// 前序遍历 DFS
class Solution {
    public Node connect(Node root) {
        if (root == null) return null;
        connectTwoNode(root.left, root.right);
        return root;
    }
    // 主要是考虑两个节点怎么连接起来,这样可以兼顾到两颗不同子树上的节点的连接问题
    void connectTwoNode(Node node1, Node node2) {
        if (node1 == null || node2 == null) return;
        node1.next = node2;
        connectTwoNode(node1.left, node1.right);
        connectTwoNode(node2.left, node2.right);
        connectTwoNode(node1.right, node2.left);
    }
}
// BFS 没什么好说的,对着模板,抄抄就行
class Solution {
    public Node connect(Node root) {
        if (root == null) return root;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i ++) {
                Node tmp = queue.poll();
                // 核心业务段,嗯,一句话
                if (i < size - 1) tmp.next = queue.peek();
                if (tmp.left != null) queue.offer(tmp.left);
                if (tmp.right != null) queue.offer(tmp.right);
            }
        }
        return root;
    }
}

二叉树转链表

在二叉树中做 DFS,大概分两种,一种是设置个全局变量,一个是函数传参,模板其实跟上面也一样

// 设置全局变量,大力出奇迹,奥里给
class Solution {
    Queue<TreeNode> queue = new LinkedList<>();

    public void function(E root) {
        if (root == null) return ;
        dfs(root);
        // 此处是操作
    }
	// 把结点放入队列中
    void dfs(E root) {
        if (root == null) return;
        // 在前面就是前序遍历
        queue.offer(root);
        dfs(root.left);
        dfs(root.right);
    }
}
// 函数传参,优雅点
class Solution {
    public void function(E root) {
        if (root == null) return ;
        Queue<E> queue = new LinkedList<>();
        queue = dfs(root, queue);
        // 此处是操作
    }

    Queue<E> dfs(E root, Queue<E> queue) {
        if (root == null) return new LinkedList(queue);
        // 在前面就是前序遍历
        queue.offer(root);
        dfs(root.left, queue);
        dfs(root.right, queue);
        return new LinkedList(queue);
    }
}

二叉树展开为链表

用 BFS 来做这题,首先肯定还是需要 DFS 整个链表的,得到顺序的链表后再去一个一个设置 left 和 right 的值;用 DFS 来做,稍微不好想一点,最后要把做拼接操作

// https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/
// 使用 BFS ,大力出奇迹,但是空间复杂度不是 O(1),但是好想出来啊,附庸风雅的用传参的形式
class Solution {
    public void flatten(TreeNode root) {
        if (root == null) return ;
        Queue<TreeNode> queue = new LinkedList<>();
        queue = dfs(root, queue);
        int size = queue.size();
        for (int i = 0; i < size - 1; i ++) {
            TreeNode tmp = queue.poll();
            tmp.left = null;
            tmp.right = queue.peek();
        }
    }

    Queue<TreeNode> dfs(TreeNode root, Queue<TreeNode> queue) {
        if (root == null) return new LinkedList(queue);
        queue.offer(root);
        dfs(root.left, queue);
        dfs(root.right, queue);
        return new LinkedList(queue);
    }
} 
// 递归,后序遍历
class Solution {
    public void flatten(TreeNode root) {
        if (root == null) return;
        flatten(root.left);
        flatten(root.right);
        // 递归中,左右子树已经拉平
        TreeNode left = root.left;
        TreeNode right = root.right;
        // 把左子树归位
        root.left = null;
        root.right = left;
        // 把右子树归位
        TreeNode tmp = root;
        while (tmp.right != null) {
            tmp = tmp.right;
        }
        // 把左子树和右子树拼接起来
        tmp.right = right;
    }
}

链表转二叉树

构造一个二叉树,主要是找到根节点位置,借此将原链表分成两半,再分而治之

最大二叉树

构造最大二叉树,首先找到根节点,也就是最大值的索引,然后分成两半,各自递归,再合并,完事。要注意的是需要构建一个 helper 函数,来帮助定义上下界

// https://leetcode-cn.com/problems/maximum-binary-tree/
class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return build(nums, 0, nums.length - 1);
    }

    TreeNode build(int[] nums, int left, int right) {
        if (left > right) return null;
        // 找到数组最大值和最大值索引
        int index = -1, maxVal = Integer.MIN_VALUE;
        for (int i = left; i <= right; i ++) {
            if (maxVal < nums[i]) {
                index = i;
                maxVal = nums[i];
            }
        }
        
        TreeNode root = new TreeNode(maxVal);
        root.left = build(nums, left, index - 1);
        root.right = build(nums, index + 1, right);
        return root;
    }
}

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

先将中序遍历的链表存入 indexMap 中,与上题的区别是上题找位置是一个一个比较找到值,而本题是用前序遍历第一个值就是根节点的特性,大体框架是一样的

// https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
class Solution {
    private Map<Integer, Integer> indexMap;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = preorder.length;
        indexMap = new HashMap<>();
        for (int i = 0; i < n; i ++) {
            indexMap.put(inorder[i], i);
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }

    public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) return null;

        int preorder_root = preorder_left;
        int inorder_root = indexMap.get(preorder[preorder_root]);
        int size_left_subtree = inorder_root - inorder_left;

        TreeNode root = new TreeNode(preorder[preorder_root]);
        root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }
}
// https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/

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

思路同上题

// https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
class Solution {
    private Map<Integer, Integer> indexMap;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int n = postorder.length;
        indexMap = new HashMap<>();
        for (int i = 0; i < n; i ++) {
            indexMap.put(inorder[i], i);
        }
        return myBuildTree(postorder, inorder, 0, n - 1, 0, n - 1);
    }

    public TreeNode myBuildTree(int[] postorder, int[] inorder, int postorder_left, int postorder_right, int inorder_left, int inorder_right) {
        if (postorder_left > postorder_right) return null;

        int postorder_root = postorder_right;
        int inorder_root = indexMap.get(postorder[postorder_root]);
        int size_left_subtree = inorder_root - inorder_left;

        TreeNode root = new TreeNode(postorder[postorder_root]);
        // 与上题区别在于边界
        root.left = myBuildTree(postorder, inorder, postorder_left, postorder_left + size_left_subtree - 1, inorder_left, inorder_root - 1);
        root.right = myBuildTree(postorder, inorder, postorder_left + size_left_subtree, postorder_right - 1, inorder_root + 1, inorder_right);
        return root;
    }
}

寻找重复的子树

大体思路是每个节点的形状转换成 String 存储在 map 中,然后递归对比每个节点,如果 map 已经有了就说明两个节点是重复子树的头节点

// https://leetcode-cn.com/problems/find-duplicate-subtrees/
class Solution {
    HashMap<String, Integer> map = new HashMap<>();
    LinkedList<TreeNode> list = new LinkedList<>();

    public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
        traverse(root);
        return list;
    }

    String traverse(TreeNode root) {
        if (root == null) return "#";
        String left = traverse(root.left);
        String right = traverse(root.right);
        String compare = left + "," + right + "," + root.val;
        map.put(compare, map.getOrDefault(compare, 0) + 1);
        if (map.get(compare) == 2) {
            list.add(root);
        }
        return compare;
    }
}
// https://leetcode-cn.com/problems/find-duplicate-subtrees/solution/xun-zhao-zhong-fu-de-zi-shu-by-leetcode/

高频面试题

二叉树的最近公共祖先

// https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
// 核心是找该结点下的子树有没有 p q 结点
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null) return root;
        else if (left != null) return left;
        else return right;
    }
}
// K神的剑指offer

Reference:

  • https://labuladong.gitee.io/algo
  • https://leetcode-cn.com/problems/