1. 树问题梳理

353 阅读17分钟

所有问题

序号题目完成

一、树的遍历

1.1 问题列表

序号题目完成
94. 二叉树的中序遍历
102. 二叉树的层序遍历
103. 二叉树的锯齿形层次遍历
1104. 二叉树寻路
637. 二叉树的层平均值
199. 二叉树的右视图
144. 二叉树的前序遍历
145. 二叉树的后序遍历
513. 找树左下角的值
100. 相同的树
剑指 Offer 26. 树的子结构

1.2 题解

94.二叉树的中序遍历

递归写法很简单,要写出迭代写法才行。

// 递归写法
class Solution {
    List<Integer> ans;

    public List<Integer> inorderTraversal(TreeNode root) {
        ans = new ArrayList<>();
        dfs(root);
        return ans;
    }

    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        ans.add(root.val);
        dfs(root.right);
    }
}
// 迭代写法
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            ans.add(root.val);
            root = root.right;
        }
        return ans;
    }
}

102. 二叉树的层序遍历

层序遍历的模板,一定要会背。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        LinkedList<TreeNode> nodes = new LinkedList<TreeNode>();
        nodes.add(root);
        while (!nodes.isEmpty()) {
            int size = nodes.size();
            List<Integer> floor = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode cur = nodes.poll();
                floor.add(cur.val);
                if (cur.left != null) {
                    nodes.add(cur.left);
                }
                if (cur.right != null) {
                    nodes.add(cur.right);
                }
            }
            ans.add(floor);
        }
        return ans;
    }
}

144. 二叉树的前序遍历

// 递归写法
class Solution {
    List<Integer> ans = new ArrayList<>();

    public List<Integer> preorderTraversal(TreeNode root) {
        dfs(root);
        return ans;
    }

    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        ans.add(root.val);
        dfs(root.left);
        dfs(root.right);
    }
}
// 迭代写法
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                ans.add(root.val);
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            root = root.right;
        }
        return ans;
    }
}

145. 二叉树的后序遍历

// 递归写法
class Solution {
    List<Integer> ans = new ArrayList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
        dfs(root);
        return ans;
    }

    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        dfs(root.right);
        ans.add(root.val);
    }
}
// 迭代写法
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode pre = null;
        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }

            root = stack.pop();
            // 1. 最左边的节点,root.right ==null
            // 2. 最左边节点的父节点
            //    2.1 此时如果有right,应该先遍历right,即else分支做的,把root先放回去,root指向right
            //        2.1.1 先把right打印出来,pre指向right
            //        2.1.2 这个时候需要打印right的parent,此时pop出来的是root,root.right == pre就是为了在right也记录完之后记录root用
            if (root.right == null || root.right == pre) {
                ans.add(root.val);
                pre = root;
                root = null;
            } else {
                // 此时有右子树,将root重新放回去,从right开始
                stack.push(root);
                root = root.right;
            }
        }
        return ans;
    }
}

199. 二叉树的右视图

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        // 记录每一层最后一个节点就可以了
        LinkedList<TreeNode> stack = new LinkedList<>();
        stack.offer(root);
        while (!stack.isEmpty()) {
            int size = stack.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = stack.poll();
                if (cur.left != null) {
                    stack.offer(cur.left);
                }
                if (cur.right != null) {
                    stack.offer(cur.right);
                }
                if (i == size - 1) {
                    ans.add(cur.val);
                }
            }
        }
        return ans;
    }
}

637. 二叉树的层平均值

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        LinkedList<TreeNode> stack = new LinkedList<>();
        stack.offer(root);
        while (!stack.isEmpty()) {
            int size = stack.size();
            double total = 0d;
            for (int i = 0; i < size; i++) {
                TreeNode cur = stack.poll();
                // 将每一层的节点的值加起来
                total += cur.val;
                if (cur.left != null) {
                    stack.offer(cur.left);
                }
                if (cur.right != null) {
                    stack.offer(cur.right);
                }
                // 在这一层即将结束时,计算这一层的平均值
                if (i == size - 1) {
                    ans.add(total / size);
                }
            }
        }
        return ans;
    }
}

103. 二叉树的锯齿形层次遍历

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        // 为true表示从右到左
        // 为false表示从左到右
        boolean reverse = false;
        while (!queue.isEmpty()) {
            int size = queue.size();
            LinkedList<Integer> floor = new LinkedList<>();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                if (reverse) {
                    // 从右到左,addFirst
                    floor.addFirst(cur.val);
                } else {
                    floor.addLast(cur.val);
                }
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            //翻转
            reverse = !reverse;
            ans.add(floor);
        }
        return ans;
    }
}

1104. 二叉树寻路

class Solution {
    public List<Integer> pathInZigZagTree(int label) {
        int row = 1, rowStart = 1;
        // 找到当前元素在哪一层
        while (rowStart * 2 <= label) {
            row++;
            rowStart *= 2;
        }
        // 如果是偶数层,把label转换
        if (row % 2 == 0) {
            label = getReverse(label, row);
        }
        LinkedList<Integer> path = new LinkedList<Integer>();
        // 开始遍历
        while (row > 0) {
            // 偶数层需要翻转,偶数层是从右到左遍历的
            if (row % 2 == 0) {
                path.addFirst(getReverse(label, row));
            } else {
                // 奇数层不用,因为奇数层是从左到右遍历的
                path.addFirst(label);
            }
            row--;
            label >>= 1;
        }
        return path;
    }

    public int getReverse(int label, int row) {
        return (1 << row - 1) + (1 << row) - 1 - label;
    }
}

513. 找树左下角的值

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        int ans = 0;
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                // 每一层的第一个元素都记录,最后ans就是最后一层的第一个元素
                if (i == 0) {
                    ans = cur.val;
                }
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
        }
        return ans;
    }
}

100. 相同的树

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        }
        if (p == null || q == null) {
            return false;
        }
        // 根节点相同并且左右子树都相同
        return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

剑指 Offer 26. 树的子结构

class Solution {

    // b是否为a的子结构
    public boolean isSubStructure(TreeNode a, TreeNode b) {
        // a走到最后了,也没有找到和b匹配的结构
        // 如果给的条件b是null,那显然是false,这里隐藏了一个条件,a和b都为空的时候,不是子结构
        if (a == null || b == null) {
            return false;
        }
        // a在递归,等找到和b相同的节点后,就可以用hasSubStruct比较了
        return hasSubStruct(a, b) || isSubStructure(a.left, b) || isSubStructure(a.right, b);
    }

    // 如果a,b根节点相同,那么b是不是a的子结构
    public boolean hasSubStruct(TreeNode a, TreeNode b) {
        // b遍历到null,说明b的所有节点都在a上遍历过了,此时b是a的子结构
        if (b == null) {
            return true;
        }
        // 如果a走到null,b没有走到null,显然不是子结构
        // 如果a也没有走到null,此时比较a和b的值,如果值不等,显然也不是子结构
        if (a == null || a.val != b.val) {
            return false;
        }
        // 继续检查左子树和右子树
        return hasSubStruct(a.left, b.left) && hasSubStruct(a.right, b.right);
    }
}

二、树的构造

2.1 问题列表

序号题目完成
1105. 从前序和中序遍历序列构造二叉树

|3|106. 从中序和后序遍历序列构造二叉树|√| |4|889. 根据前序和后序遍历序列构造二叉树|√| |2|剑指 Offer 07. 重建二叉树|√|

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

相同问题:剑指 Offer 07. 重建二叉树

这个题目需要掌握递归和迭代两种写法。

// 递归
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    public TreeNode buildTree(int[] preorder, int start1, int end1, int[] inorder, int start2, int end2) {
        if (start1 > end1 || start2 > end2) {
            return null;
        }
        int rootVal = preorder[start1];
        TreeNode root = new TreeNode(rootVal);
        int leftSize = 0;
        for (int i = start2; i <= end2; i++) {
            if (inorder[i] == rootVal) {
                break;
            }
            leftSize++;
        }
        root.left = buildTree(preorder, start1 + 1, end1, inorder, start2, start2 + leftSize - 1);
        root.right = buildTree(preorder, start1 + leftSize + 1, end1, inorder, start2 + leftSize + 1, end2);
        return root;
    }
}
// 迭代的写法,需要背
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || preorder.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[0]);
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        stack.push(root);

        int inorderIndex = 0;
        for (int i = 1; i < preorder.length; i++) {
            TreeNode node = stack.peek();
            int preorderVal = preorder[i];
            // 还没有走到最左边
            if (node.val != inorder[inorderIndex]) {
                node.left = new TreeNode(preorderVal);
                stack.push(node.left);
            } else {
                // 栈顶的元素和当前index是相等的,说明走到了最左边,此时需要考虑right
                while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
                    node = stack.pop();
                    inorderIndex++;
                }
                node.right = new TreeNode(preorderVal);
                stack.push(node.right);
            }
        }
        return root;
    }
}

106.从中序和后序遍历序列构造二叉树

// 递归写法
class Solution {
    Map<Integer, Integer> rootLocation = new HashMap<>();

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        for (int i = 0; i < inorder.length; i++) {
            rootLocation.put(inorder[i], i);
        }
        return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
    }

    public TreeNode build(int[] inorder, int start1, int end1, int[] postorder, int start2, int end2) {
        if (start1 > end1 || start2 > end2) {
            return null;
        }
        int rootVal = postorder[end2];
        int rootIn = rootLocation.get(rootVal);
        int leftSize = rootIn - start1;
        TreeNode root = new TreeNode(rootVal);
        root.left = build(inorder, start1, rootIn - 1, postorder, start2, start2 + leftSize - 1);
        root.right = build(inorder, rootIn + 1, end1, postorder, start2 + leftSize, end2 - 1);
        return root;
    }
}

889.根据前序和后序遍历序列构造二叉树

// 递归写法
class Solution {
    Map<Integer, Integer> rootLocation = new HashMap<>();

    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        for (int i = 0; i < postorder.length; i++) {
            rootLocation.put(postorder[i], i);
        }
        return buildTree(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1);
    }

    public TreeNode buildTree(int[] preorder, int start1, int end1, int[] postorder, int start2, int end2) {
        if (start1 > end1 || start2 > end2) {
            return null;
        }
        // 只要一个元素的时候,直接返回
        // 因为下面需要取start1后一个元素作为left,只有一个元素时,会报错
        if (start1 == end1) {
            return new TreeNode(preorder[start1]);
        }
        int rootVal = preorder[start1];
        TreeNode root = new TreeNode(rootVal);
        int left = preorder[start1 + 1];
        int index = rootLocation.get(left);
        int leftSize = index - start2 + 1;
        root.left = buildTree(preorder, start1 + 1, start1 + leftSize, postorder, start2, index);
        root.right = buildTree(preorder, start1 + 1 + leftSize, end1, postorder, index + 1, end2 - 1);

        return root;
    }
}

三、树的深度和宽度

其实深度或者直径也是依赖树的遍历。

3.1 问题列表

序号题目完成
104. 二叉树的最大深度
剑指 Offer 55 - I. 二叉树的深度
559. N 叉树的最大深度
111. 二叉树的最小深度
993. 二叉树的堂兄弟节点
543. 二叉树的直径
1522. N 叉树的直径
1245. 树的直径
2246. 相邻字符不同的最长路径
662. 二叉树最大宽度

3.2 题解

解题思路:遍历二叉树。

104. 二叉树的最大深度

相同问题:剑指 Offer 55 - I. 二叉树的深度

// 递归,利用左右子树的深度
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        int maxDepth = Integer.MIN_VALUE;
        // 这里加上left!=null的判断,是因为如果不加,那叶子节点的深度会被清零
        if (root.left != null) {
            maxDepth = Math.max(maxDepth(root.left), maxDepth);
        }
        if (root.right != null) {
            maxDepth = Math.max(maxDepth(root.right), maxDepth);
        }
        return maxDepth + 1;
    }
}

//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
// 同样是递归,但是利用了depth这个来做为层数
class Solution {
    int depth;
    int maxDepth;

    public int maxDepth(TreeNode root) {
        dfs(root);
        return maxDepth;
    }

    public void dfs(TreeNode node) {
        if (node == null) {
            return;
        }
        depth++;
        if (node.left == null && node.right == null) {
            maxDepth = Math.max(maxDepth, depth);
        }
        dfs(node.left);
        dfs(node.right);
        depth--;
    }
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度

559. N 叉树的最大深度

和二叉树的区别就在于遍历子树的方法不同,其他都一样的。

class Solution {
    public int maxDepth(Node root) {
        if (root == null) {
            return 0;
        }
        if (root.children == null || root.children.isEmpty()) {
            return 1;
        }
        int maxDepth = Integer.MIN_VALUE;
        // 从所有子树的最大深度中找到最大值
        for (Node child : root.children) {
            maxDepth = Math.max(maxDepth(child), maxDepth);
        }
        return maxDepth + 1;
    }
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度

111. 二叉树的最小深度

// 递归解法
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        int minDepth = Integer.MAX_VALUE;
        // 得到左子树的最小深度
        if(root.left!=null){
            minDepth = Math.min(minDepth(root.left), minDepth);
        }
        // 得到右子树的最小深度
        if(root.right!=null){
            minDepth = Math.min(minDepth(root.right), minDepth);
        }
        // 最短的左子树和右子树深度加上自身节点
        return minDepth+1;
    }
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
//迭代解法
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        // 深度就是层数,用一个字段来记录层数
        int floor = 1;
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i=0;i<size;i++){
                TreeNode cur = queue.pop();
                // 找到的第一个叶子节点就是最小的深度
                if(cur.left == null && cur.right == null){
                    return floor;
                }
                if(cur.left!=null){
                    queue.offer(cur.left);
                }
                if(cur.right!=null){
                    queue.offer(cur.right);
                }
            }
            floor++;
        }
        return floor;
    }
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(n),队列需要保存所有节点

543. 二叉树的直径

// DFS
class Solution {
    private int max;

    public int diameterOfBinaryTree(TreeNode root) {
        // 注意直径是边数,不是节点数,直径=左子树最长的深度 + 右子树最长的深度
        maxDepth(root);
        return max;
    }

    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        // 左子节点的最大深度
        int left = maxDepth(root.left);
        // 右子节点的最大深度
        int right = maxDepth(root.right);
        max = Math.max(max, left + right);
        // 返回当前节点的最大深度
        return Math.max(left, right) + 1;
    }
}
//leetcode submit region end(Prohibit modification and deletion)
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度

1522. N 叉树的直径

获取最大的两个子树深度可以用优先队列,也可以直接写:

int first = 0;
int second = 0;
for (Node child : root.children) {
    int tmp = maxDepth(child);
    if (tmp > first) {
        // 把原来最大的置为第二大值
        second = first;
        // 更新最大值
        first = tmp;
    } else if (tmp > second) {
        // 只更新第二大值
        second = tmp;
    }
}
class Solution {
    int max;

    public int diameter(Node root) {
        // 所有子树中最深的两个子树的深度之和
        maxDepth(root);
        return max;
    }

    public int maxDepth(Node root) {
        if (root == null) {
            return 0;
        }

        if (root.children != null && !root.children.isEmpty()) {
            // 用一个大顶堆来实现获取最大的两个子树深度
            PriorityQueue<Integer> maxQueue = new PriorityQueue<>((o1, o2) -> {
                return o2 - o1;
            });
            for (Node child : root.children) {
                maxQueue.add(maxDepth(child));
            }
            int first = maxQueue.isEmpty() ? 0 : maxQueue.poll();
            int second = maxQueue.isEmpty() ? 0 : maxQueue.poll();
            // 计算最深的两条路径之和中的最大值
            max = Math.max(first + second, max);
            return first + 1;
        }
        return 1;
    }
}
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度

993. 二叉树的堂兄弟节点

class Solution {
    int xParent;
    int yParent;
    int xDepth;
    int yDepth;
    boolean xFound = false;
    boolean yFound = false;

    public boolean isCousins(TreeNode root, int x, int y) {
        dfs(root, 0, -1, x, y);
        // 找到x和y的深度已经他们的父节点,最后判断是否为堂兄弟
        return xDepth == yDepth && xParent != yParent;
    }

    public void dfs(TreeNode node, int depth, int parent, int x, int y) {
        if (node == null) {
            return;
        }
        // 更新深度和父节点
        if (node.val == x) {
            xFound = true;
            xParent = parent;
            xDepth = depth;
        } else if (node.val == y) {
            yFound = true;
            yParent = parent;
            yDepth = depth;
        }

        // 两个节点都找到了,就不用再遍历了
        if (xFound && yFound) {
            return;
        }

        // 没找到,继续left
        dfs(node.left, depth + 1, node.val, x, y);

        // 两个节点都找到了,就不用再遍历了
        if (xFound && yFound) {
            return;
        }
        
        // 没找到,继续right
        dfs(node.right, depth + 1, node.val, x, y);
    }
}
//BFS解法
class Solution {
    public boolean isCousins(TreeNode root, int x, int y) {
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        // 层序遍历,堂兄弟必然在同一层
        while (!queue.isEmpty()) {
            int size = queue.size();
            boolean matchX = false;
            boolean matchY = false;
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                if (cur.val == x) {
                    matchX = true;
                }
                if (cur.val == y) {
                    matchY = true;
                }
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
                // 如果x和y为亲兄弟,直接退出
                if (cur.left != null && cur.right != null) {
                    if ((cur.left.val == x || cur.left.val == y) && (cur.right.val == x || cur.right.val == y)) {
                        return false;
                    }
                }
            }
            if (matchX && matchY) {
                // 找到了堂兄弟,直接退出
                return true;
            } else if (matchX || matchY) {
                // 找到一个节点,另一个没找到,此时不可能存在堂兄弟了,所以也直接退出
                return false;
            }
        }
        // 遍历了所有节点都没有找到堂兄弟节点,此时退出了
        return false;
    }
}

[To Review]1245. 树的直径

在做图章节的时候,需要和这个结合再加深理解。

多叉树其实就是无向图吧。

class Solution {
    int max = Integer.MIN_VALUE;
    public int treeDiameter(int[][] edges) {
        if (edges.length == 0) {
            return 0;
        }
        List<Integer>[] mappings = new List[edges.length + 1];
        for (int i = 0; i < mappings.length; i++) {
            mappings[i] = new ArrayList<>();
        }
        for (int[] edge : edges) {
            int from = edge[0];
            int to = edge[1];
            mappings[from].add(to);
        }
        dfs(0, mappings);
        return max;
    }

    // root节点的最大深度
    public int dfs(Integer root, List<Integer>[] mappings) {
        // 节点为空了,深度没有意义,返回0
        if (root == null) {
            return 0;
        }
        // 防止重复
        visited[root] = true;
        // 不存在子树,则深度为1
        List<Integer> children = mappings[root];
        if (children.size() == 0) {
            return 1;
        }

        int first = 0;
        int second = 0;
        for (Integer child : children) {
            int tmp = dfs(child, mappings);
            if (tmp > first) {
                second = first;
                first = tmp;
            } else if (tmp > second) {
                second = tmp;
            }
        }

        max = Math.max(first + second, max);
        return first + 1;
    }
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)

2246. 相邻字符不同的最长路径

其实也可以转换为N叉树的直径问题。

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    int max = 1;
    String source;

    public int longestPath(int[] parent, String s) {
        int n = parent.length;
        List<Integer>[] childrens = new List[n];
        source = s;
        for (int i = 0; i < n; i++) {
            childrens[i] = new ArrayList<>();
        }
        for (int i = 1; i < n; i++) {
            childrens[parent[i]].add(i);
        }
        dfs(0, childrens);
        return max;
    }

    public int dfs(Integer root, List<Integer>[] childrens) {
        // 节点为空了,深度没有意义,返回0
        if (root == null) {
            return 0;
        }
        // 不存在子树,则深度为1
        List<Integer> children = childrens[root];
        if (children.size() == 0) {
            return 1;
        }

        int first = 0;
        int second = 0;
        for (Integer child : children) {
            int tmp = dfs(child, childrens);
            if (source.charAt(child) == source.charAt(root)) {
                continue;
            }
            if (tmp > first) {
                second = first;
                first = tmp;
            } else if (tmp > second) {
                second = tmp;
            }
        }

        max = Math.max(first + second + 1, max);
        return first + 1;
    }
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)

四、树的转换

4.1 问题列表

序号题目完成
538. 把二叉树转换成累加树
226. 翻转二叉树
剑指 Offer 27. 二叉树的镜像
617. 合并二叉树
971. 翻转二叉树以匹配先序遍历
114. 二叉树展开为链表(先序遍历)
109. 有序链表转换二叉搜索树
剑指 Offer 36. 二叉搜索树与双向链表
426. 将二叉搜索树转化为排序的双向链表

4.2 题解

  1. 把二叉树转换成累加树
class Solution {
    int sum=0;
    public TreeNode convertBST(TreeNode root) {
        if(root == null){
            return null;
        }
        // 遍历右子树
        convertBST(root.right);
        // 处理root的val
        sum += root.val;
        root.val = sum;
        // 遍历左子树
        convertBST(root.left);
        return root;
    }
}

226. 翻转二叉树

class Solution {
    // 递归函数的定义:将root的左右子树翻转
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        // 得到翻转后的左子树
        TreeNode left = invertTree(root.left);
        // 得到翻转后的右子树
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        // 返回根节点
        return root;
    }
}

617. 合并二叉树

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        // 都为null的时候返回null
        if (root1 == null && root2 == null) {
            return null;
        }
        // 重新new一个节点
        TreeNode root = new TreeNode();
        // 计算节点的值
        root.val = (root1 == null ? 0 : root1.val) + (root2 == null ? 0 : root2.val);

        // 获得左节点
        TreeNode left = mergeTrees(root1 == null ? null : root1.left, root2 == null ? null : root2.left);
        // 获得右节点
        TreeNode right = mergeTrees(root1 == null ? null : root1.right, root2 == null ? null : root2.right);
        root.left = left;
        root.right = right;
        return root;
    }
}

114. 二叉树展开为链表

class Solution {
    // 定义:将以 root 为根的树拉平为链表
    public void flatten(TreeNode root) {
        if (root == null) {
            return;
        }
        // 先把左右子树拉平
        flatten(root.left);
        flatten(root.right);

        TreeNode left = root.left;
        TreeNode right = root.right;
        // 将root的左子树置空
        root.left = null;
        // 将root的右子树连接到left
        root.right = left;

        // 将右子树接上去,注意要找到left的最后一个节点来接
        TreeNode p = root;
        while (p.right != null) {
            p = p.right;
        }
        p.right = right;
    }
}

426. 将二叉搜索树转化为排序的双向链表

利用了中序遍历的顺序,并且用了一个last节点来记录上一次遍历结果。

相同问题:剑指 Offer 36. 二叉搜索树与双向链表

class Solution {
    Node first;
    Node last;
    public Node treeToDoublyList(Node root) {
        if(root == null){
            return null;
        }
        helper(root);
        last.right = first;
        first.left = last;
        return first;
    }

    public void helper(Node root){
        if(root == null){
            return;
        }
        helper(root.left);
        if(last!=null){
            root.left = last;
            last.right = root;
        }else{
            // 第一次遇到的节点,肯定是最小的节点
            first = root;
        }
        last = root;
        helper(root.right);
    }
}

五、路径问题

5.1 问题列表

序号题目完成
1112. 路径总和
2113. 路径总和 II
3剑指 Offer 34. 二叉树中和为某一值的路径
4437. 路径总和 III
5剑指 Offer II 050. 向下的路径节点之和
6124. 二叉树中的最大路径和
7剑指 Offer II 051. 节点之和最大的路径
8129. 求根节点到叶节点数字之和
9剑指 Offer II 049. 从根节点到叶节点的路径数字之和
10666. 路径总和 IV
863. 二叉树中所有距离为k的结点

5.2 题解

5.2.1 最长的路径

  1. 求最深的那条路径
  2. 给一棵边权树树找到最大路径,要找到两个端点怎么办

5.2.2 路径总和

5.2.2.1 从根节点出发到某一节点

112. 路径总和

是否存在目标的路径和

class Solution {
    int pathSum;
    boolean rs;

    public boolean hasPathSum(TreeNode root, int targetSum) {
        dfs(root, targetSum);
        return rs;
    }

    public void dfs(TreeNode root, int target) {
        if (root == null) {
            return;
        }

        pathSum += root.val;
        // root是叶子节点,并且路径和等于目标
        if (root.left == null && root.right == null && pathSum == target) {
            rs = true;
            return;
        }
        dfs(root.left, target);
        dfs(root.right, target);
        pathSum -= root.val;
    }
}
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度

113. 路径总和 II

输出所有目标路径和的路径

相同问题:剑指 Offer 34. 二叉树中和为某一值的路径

class Solution {
    List<List<Integer>> rs;

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        rs = new ArrayList<>();
        dfs(root, targetSum, 0, new LinkedList<Integer>());
        return rs;
    }

    public void dfs(TreeNode root, int target, int pathSum, LinkedList<Integer> paths) {
        if (root == null) {
            return;
        }
        // 将当前节点记入路径和
        pathSum += root.val;
        paths.add(root.val);

        // root是叶子节点,并且路径和等于目标
        if (root.left == null && root.right == null && pathSum == target) {
            // 记录该路径
            rs.add(new ArrayList<>(paths));
        }
        dfs(root.left, target, pathSum, paths);
        dfs(root.right, target, pathSum, paths);

        // 将当前节点从路径和里删除
        pathSum -= root.val;
        paths.removeLast();
    }
}

5.2.2.1 从任意节点出发

437. 路径总和 III

相同问题:剑指 Offer II 050. 向下的路径节点之和

输出所有目标路径和的路径条数

遍历方向始终是向下

class Solution {
    // 递归函数的定义,root(包含自己)的所有路径中,路径和等于target的路径条数
    // 退出条件,root==null,此时没有其他路径,所以应该返回0
    public int pathSum(TreeNode root, int targetSum) {
        if(root == null){
            return 0;
        }
        int ret = rootSum(root, (long) targetSum);
        ret += pathSum(root.left, targetSum);
        ret += pathSum(root.right, targetSum);
        return ret;
    }

    // 递归函数的定义,从root节点出发,遍历到的路径中,路径和等于target的路径条数
    // 退出条件,root==null,此时表示从null出发,那肯定是0
    public int rootSum(TreeNode root, Long targetSum) {
        int ret = 0;
        // 退出条件为root==null
        if (root == null) {
            return 0;
        }
        // 发现了目标路径和,结果+1,此时不能退出遍历,还需要继续遍历下去
        if (root.val == targetSum) {
            ret++;
        }
        ret += rootSum(root.left, targetSum - root.val);
        ret += rootSum(root.right, targetSum - root.val);
        return ret;
    }
}
// 时间复杂度:O(n*n)
// 空间复杂度:O(height)
// 前缀和解法
class Solution {
    // 前缀和出现的次数
    Map<Long, Integer> map = new HashMap<>();

    public int pathSum(TreeNode root, int targetSum) {
        map.put(0L, 1);
        return dfs(root, targetSum, 0L);
    }

    //在root为根节点的子树中,存在节点之间路径和为target的路径总数
    public int dfs(TreeNode root, int target, long preSum) {
        if (root == null) {
            return 0;
        }
        int res = 0;
        // 计算root到当前的前缀和
        preSum += root.val;
        res += getMatchedNodeCount(preSum, target);
        recordPreSum(preSum);
        // 再加上左右子树下的满足的路径和数
        res += dfs(root.left, target, preSum);
        res += dfs(root.right, target, preSum);
        removePreSum(preSum);
        return res;
    }

    // 如果存在node1到node2的路径和为target,那应该满足preSum2- preSum1 = target
    // 所以想要知道满足preSum1的路径和的数量,应该取key=preSum2 - target
    public int getMatchedNodeCount(long curSum, int target) {
        return map.getOrDefault(curSum - target, 0);
    }

    public void recordPreSum(long preSum) {
        map.put(preSum, map.getOrDefault(preSum, 0) + 1);
    }

    public void removePreSum(long preSum) {
        map.put(preSum, map.get(preSum) - 1);
    }
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)

5.2.2.3 结点之和最大的路径

124. 二叉树中的最大路径和

相同问题:剑指 Offer II 051. 节点之和最大的路径

class Solution {
    int max = Integer.MIN_VALUE;

    // root下的所有路径中的最大路径和
    public int maxPathSum(TreeNode root) {
        findMax(root);
        return max;
    }

    // 一个节点的最大贡献值
    public int findMax(TreeNode root) {
        if (root == null) {
            return 0;
        }
        // 左子树的最大贡献值
        int left = Math.max(findMax(root.left), 0);
        // 右子树的最大贡献值
        int right = Math.max(findMax(root.right), 0);
        // 在遍历树的时候寻找每一个节点的最大路径和
        max = Math.max(max, left + right + root.val);
        // 左右子树可能为全负,此时只需要节点本身就够了
        return root.val + Math.max(left, right);
    }
}

//时间复杂度:O(n)
//空间复杂度:O(height)

5.2.2.4 从根节点到某一节点的路径

129. 求根节点到叶节点数字之和

相同问题:剑指 Offer II 049. 从根节点到叶节点的路径数字之和

class Solution {
    List<String> paths;

    public int sumNumbers(TreeNode root) {
        paths = new ArrayList<>();
        traverse(root, new StringBuilder());
        int total = 0;
        for (String path : paths) {
            total += Integer.parseInt(path);
        }
        return total;
    }

    public void traverse(TreeNode root, StringBuilder path) {
        if (root == null) {
            return;
        }
        path.append(root.val);
        if (root.left == null && root.right == null) {
            paths.add(path.toString());
        }
        traverse(root.left, path);
        traverse(root.right, path);
        path.deleteCharAt(path.length()-1);
    }
}
//leetcode submit region end(Prohibit modification and deletion)

利用前缀和求解:

  1. 计算出每个节点上的数字
  2. 然后累加到root节点(正好用递归)

从leetcode官方题解偷的图,解释的很清楚

// 前缀和解法
class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int preSum){
        if(root == null){
            return 0;
        }
        // 计算当前节点的值
        int sum = preSum * 10 +root.val;
        if(root.left == null && root.right ==null){
            return sum;
        }else{
            // 左右节点加起来最后累加到根节点
            return dfs(root.left, sum) + dfs(root.right, sum);
        }
    }
}
// BFS遍历
import java.util.LinkedList;
class Solution {
    public int sumNumbers(TreeNode root) {
        LinkedList<TreeNode> nodes = new LinkedList<>();
        // 用一个队列记录当前节点的值
        LinkedList<Integer> nums = new LinkedList<>();
        nodes.add(root);
        nums.add(root.val);
        int total = 0;
        while (!nodes.isEmpty()) {
            int size = nodes.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = nodes.poll();
                int val = nums.pop();
                // 到叶子节点时,计算路径总和
                if (cur.left == null && cur.right == null) {
                    total += val;
                }

                if (cur.left != null) {
                    nodes.add(cur.left);
                    nums.add(val * 10 + cur.left.val);
                }
                if (cur.right != null) {
                    nodes.add(cur.right);
                    nums.add(val * 10 + cur.right.val);
                }
            }
        }
        return total;
    }
}
// 时间复杂度:O(n),每个节点遍历一次
// 空间复杂度:O(n),每个节点记录一次

5.2.2.5 其他

666. 路径总和 IV

其实计算从根到所有叶子的路径总和并不难,这一题的难点在于,题目给的是一串数字,要将这些数字转换为节点。

这里需要一些完全二叉树的知识。

import java.util.HashMap;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    // 节点位置和原数字的映射关系
    Map<Integer, Integer> mapping = new HashMap<>();
    int total = 0;

    public int pathSum(int[] nums) {
        for (int num : nums) {
            mapping.put(getNode(num), num);
        }
        // 从第0个数字开始遍历
        dfs(nums[0], 0);
        return total;
    }

    public void dfs(Integer root, int sum) {
        // 节点为空,退出
        if (root == null) {
            return;
        }
        int val = getValue(root);
        sum += val;

        Integer left = getLeft(root);
        Integer right = getRight(root);
        // 一个叶子节点就是一条路径
        if (left == null && right == null) {
            // 计算到总和里
            total += sum;
        }
        dfs(left, sum);
        dfs(right, sum);
        sum -= val;
    }

    public int getNode(int num) {
        // 百分位作为层数
        int floor = num / 100;
        // 十分位作为位置
        int index = (num - floor * 100) / 10;
        // 个位作为值
        int value = num - floor * 100 - index * 10;
        // 计算得到当前节点的位置
        return (int) Math.pow(2, floor - 1) - 1 + index;
    }

    public int getValue(int num) {
        // 百分位作为层数
        int floor = num / 100;
        // 十分位作为位置
        int index = (num - floor * 100) / 10;
        // 个位作为值
        return num - floor * 100 - index * 10;
    }

    public boolean isNull(int num) {
        int pos = getNode(num);
        // 不存在,我们可以认为节点为空
        return !mapping.containsKey(pos);
    }

    public Integer getLeft(int num) {
        int pos = getNode(num);
        return mapping.get(pos * 2);
    }

    public Integer getRight(int num) {
        int pos = getNode(num);
        return mapping.get(pos * 2 + 1);
    }


}
//leetcode submit region end(Prohibit modification and deletion)
  1. 二叉树输出给定节点到目标节点的路径

六、祖先问题

6.1 问题列表

序号题目完成
1[236]二叉树的最近公共祖先
2[剑指 Offer 68 - II]二叉树的最近公共祖先
3[1644]二叉树的最近公共祖先 II
4[1650]二叉树的最近公共祖先 III
5[1676]二叉树的最近公共祖先 IV
6[235]二叉搜索树的最近公共祖先
7[剑指 Offer 68 - I]二叉搜索树的最近公共祖先
8[1483]树节点的第 K 个祖先
9993. 二叉树的堂兄弟节点

6.2 题解

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

两种解法:

  1. 用DFS遍历,定义f(x)为,x的自身或者子节点中包含p或q
  2. 先记录每一个节点的父节点,然后对其中一个往上找,找p和q的父节点,有没有重合的,第一个重合的父节点就是最近的公共祖先
// 解法一
class Solution {
    TreeNode ans = null;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root, p, q);
        return ans;
    }

    // root的子节点或自身包含p或q
    public boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return false;
        }
        boolean left = dfs(root.left, p, q);
        boolean right = dfs(root.right, p, q);

        // 1. 同时包含p或q的时候,这是最理想的状态
        // 2. 当前节点就是p或者q,此时left和right不可能同时为true
        if ((left && right) || ((root.val == p.val || root.val == q.val) && (right || left))) {
            ans = root;
        }
        // 左子树有、右子树有、当前节点就是
        return left || right || (root.val == p.val || root.val == q.val);
    }
}
// 解法二
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        Map<Integer, TreeNode> parent = new HashMap<>();
        dfs(root, parent);

        // 记录p的所有父节点
        Set<Integer> visited = new HashSet<>();
        while (p != null) {
            visited.add(p.val);
            p = parent.get(p.val);
        }
        // 再去遍历q的父节点,q的父节点肯定在visited中出现
        while (q != null) {
            if (visited.contains(q.val)) {
                return q;
            }
            q = parent.get(q.val);
        }
        // 肯定可以找到最近公共祖先,最后返回null,只是方法需要一个返回值
        return null;
    }

    public void dfs(TreeNode root, Map<Integer, TreeNode> parent) {
        if (root.left != null) {
            parent.put(root.left.val, root);
            dfs(root.left, parent);
        }
        if (root.right != null) {
            parent.put(root.right.val, root);
            dfs(root.right, parent);
        }
    }
}

1483. 树节点的第 K 个祖先

最容易想到的是从根节点往上遍历,就像236里的写法。不过这种写法是会超时的。

class TreeAncestor {
    int[] parents;
    public TreeAncestor(int n, int[] parent) {
        parents = parent;
    }

    public int getKthAncestor(int node, int k) {
        if(k == 0){
            return node;
        }
        if(k <0){
            return -1;
        }
        // 拿到当前节点的父节点
        int parent = parents[node];
        if(parent == -1){
            return -1;
        }
        return getKthAncestor(parent, k-1);
    }
}

[1644]二叉树的最近公共祖先 II

解法一:记录parents节点,然后先将p的parents节点都记录下来,再去遍历q的每一个parent节点,当可以与p的parent节点相遇,那么就找到了最近公共祖先。 解法二:利用fx,fx定义为root的子树下或自身存在p或q的节点。

//leetcode submit region begin(Prohibit modification and deletion)

import java.util.HashSet;

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    TreeNode ans = null;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root, p, q, false);
        return ans;
    }

    public boolean dfs(TreeNode root, TreeNode p, TreeNode q, boolean exist) {
        if (root == null) {
            return false;
        }
        boolean left = dfs(root.left, p, q, exist);
        boolean right = dfs(root.right, p, q, exist);
        if ((left && right) || ((root.val == p.val || root.val == q.val) && (left || right))) {
            if (!exist) {
                // 当前节点就是LCA
                ans = root;
                exist = true;
            }
        }
        return left || right || (root.val == p.val || root.val == q.val);
    }
}
//leetcode submit region end(Prohibit modification and deletion)

[1650]二叉树的最近公共祖先 III

将p的parents节点都记录下来,再去遍历q的每一个parent节点,当可以与p的parent节点相遇,那么就找到了最近公共祖先。

解法1
class Solution {
    public Node lowestCommonAncestor(Node p, Node q) {
        Set<Integer> visited = new HashSet<>();
        while (p != null) {
            visited.add(p.val);
            p = p.parent;
        }
        while (q != null) {
            if (visited.contains(q.val)) {
                return q;
            }
            q = q.parent;
        }
        return null;
    }
}
// 解法2
class Solution {
    public Node lowestCommonAncestor(Node p, Node q) {
        // 可以认为是求相交链表
        Node pp = p;
        Node qq = q;
        while (pp != qq) {
            if (pp == null) {
                pp = q;
            } else {
                pp = pp.parent;
            }

            if (qq == null) {
                qq = p;
            } else {
                qq = qq.parent;
            }
        }
        return pp;
    }
}

[1676]二叉树的最近公共祖先 IV

// 暴力解法
class Solution {
    Map<Integer, TreeNode> parents = new HashMap<>();
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) {
        dfs(root);
        Map<Integer, Integer> visited = new HashMap<>();
        TreeNode p = nodes[0];
        int start = 0;
        while (p != null) {
            visited.put(p.val, start++);
            p = parents.get(p.val);
        }
        TreeNode ans = nodes[0];
        int max = Integer.MIN_VALUE;
        for (int i=1; i<nodes.length; i++) {
            TreeNode q = nodes[i];
            while (q != null) {
                if (visited.containsKey(q.val)) {
                    int cur = visited.get(q.val);
                    if (cur > max) {
                        ans = q;
                        max = cur;
                    }
                    break;
                }
                q = parents.get(q.val);
            }
        }
        return ans;
    }

    public void dfs(TreeNode root) {
        if (root.left != null) {
            parents.put(root.left.val, root);
            dfs(root.left);
        }
        if (root.right != null) {
            parents.put(root.right.val, root);
            dfs(root.right);
        }
    }
}
//leetcode submit region end(Prohibit modification and deletion)
// 递归解法
class Solution {
    // 递归定义:返回root的子树上nodes中的节点的最近公共祖先
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) {
        if (root == null) {
            return null;
        }

        // 如果nodes节点里包含root节点,显然最近的公共祖先就是root
        // 因为在这一次的遍历里,root是根节点
        for (TreeNode node : nodes) {
            if (root == node) {
                return root;
            }
        }

        // 获取左右子树各自属于nodes中的节点的最近公共祖先
        TreeNode left = lowestCommonAncestor(root.left, nodes);
        TreeNode right = lowestCommonAncestor(root.right, nodes);

        // 若左右子树均存在nodes中的节点,则最近公共祖先就是root
        // 因为nodes里的节点分散在root的左右子树上了,单独的左子树或右子树无法完全覆盖所有的nodes节点
        if (left != null && right != null) {
            return root;
        }
        // 1. left和right都为null,在本题中是不可能的,因为肯定存在公共祖先,不过也不影响,当都为null时返回null即可
        // 2. left和right有一个为null,不为null的子树上的公共祖先,即为所有属于nodes中节点的公共祖先
        return left != null ? left : right;
    }
}

[235]二叉搜索树的最近公共祖先

相同问题:[剑指 Offer 68 - I]二叉搜索树的最近公共祖先

二叉搜索树的特点是:所有左子树的值都小于根节点,所有右子树的值都大于根节点。

分析:

  1. 如果p在左,q在右,此时可能为root
  2. 如果p、q都在左,那肯定不在root,至少是root的左子树
  3. 如果p、q都在右,那也肯定不在root,至少是root的右子树
// 找到p<=root<=q的节点
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode ancestor = root;
        while (true) {
            if (p.val < ancestor.val && q.val < ancestor.val) {
                ancestor = ancestor.left;
            } else if (p.val > ancestor.val && q.val > ancestor.val) {
                ancestor = ancestor.right;
            } else {
                break;
            }
        }
        return ancestor;
    }
}

993. 二叉树的堂兄弟节点

class Solution {
    // x 的信息
    int x;
    TreeNode xParent;
    int xDepth;
    boolean xFound = false;

    // y 的信息
    int y;
    TreeNode yParent;
    int yDepth;
    boolean yFound = false;

    public boolean isCousins(TreeNode root, int x, int y) {
        this.x = x;
        this.y = y;
        dfs(root, 0, null);
        return xDepth == yDepth && xParent != yParent;
    }

    public void dfs(TreeNode node, int depth, TreeNode parent) {
        if (node == null) {
            return;
        }

        if (node.val == x) {
            xParent = parent;
            xDepth = depth;
            xFound = true;
        } else if (node.val == y) {
            yParent = parent;
            yDepth = depth;
            yFound = true;
        }

        // 如果两个节点都找到了,就可以提前退出遍历
        // 即使不提前退出,对最坏情况下的时间复杂度也不会有影响
        if (xFound && yFound) {
            return;
        }

        dfs(node.left, depth + 1, node);

        if (xFound && yFound) {
            return;
        }

        dfs(node.right, depth + 1, node);
    }
}

七、树的序列化和反序列化

7.1 问题列表

序号问题描述完成
297. 二叉树的序列化与反序列化
428. 序列化和反序列化 N 叉树
449. 序列化和反序列化二叉搜索树

297. 二叉树的序列化与反序列化

public class Codec {
    private String SEP = ",";
    private String NULL = "#";

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sp = new StringBuilder();
        dfs(root, sp);
        return sp.deleteCharAt(sp.length() - 1).toString();
    }

    public void dfs(TreeNode root, StringBuilder sb) {
        if (root == null) {
            sb.append(NULL).append(SEP);
            return;
        }
        sb.append(root.val).append(SEP);
        dfs(root.left, sb);
        dfs(root.right, sb);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] datas = data.split(SEP);
        LinkedList<String> vals = new LinkedList<>();
        for (String d : datas) {
            vals.add(d);
        }
        return construct(vals);
    }

    public TreeNode construct(LinkedList<String> vals) {
        if (vals.isEmpty()) {
            return null;
        }
        String first = vals.removeFirst();
        if (first.equals(NULL)) {
            return null;
        }
        TreeNode root = new TreeNode(Integer.parseInt(first));
        root.left = construct(vals);
        root.right = construct(vals);
        return root;
    }
}
  • 时间复杂度:O(n),每一个节点都需要访问一次
  • 空间复杂度:O(n),用了一个临时链表

449. 序列化和反序列化二叉搜索树

用一般二叉树的序列化和反序列化也是可以AC的。还有优化的空间吗?

public class Codec {
    public String serialize(TreeNode root) {
        List<Integer> list = new ArrayList<Integer>();
        postOrder(root, list);
        String str = list.toString();
        return str.substring(1, str.length() - 1);
    }

    public TreeNode deserialize(String data) {
        if (data.isEmpty()) {
            return null;
        }
        String[] arr = data.split(", ");
        Deque<Integer> stack = new ArrayDeque<Integer>();
        int length = arr.length;
        for (int i = 0; i < length; i++) {
            stack.push(Integer.parseInt(arr[i]));
        }
        return construct(Integer.MIN_VALUE, Integer.MAX_VALUE, stack);
    }

    private void postOrder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        postOrder(root.left, list);
        postOrder(root.right, list);
        // 后序遍历
        list.add(root.val);
    }

    private TreeNode construct(int lower, int upper, Deque<Integer> stack) {
        if (stack.isEmpty() || stack.peek() < lower || stack.peek() > upper) {
            return null;
        }
        int val = stack.pop();
        TreeNode root = new TreeNode(val);
        root.right = construct(val, upper, stack);
        root.left = construct(lower, val, stack);
        return root;
    }
}

428. 序列化和反序列化 N 叉树

解题思路:
1、序列化:用[]将children围起来;
2、反序列化:用栈来实现层级关系。

class Codec {
    private String SEP = ",";
    private String START_TAG = "[";
    private String END_TAG = "]";

    // Encodes a tree to a single string.
    public String serialize(Node root) {
        if (root == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        dfs(root, sb);
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    public void dfs(Node root, StringBuilder sb) {
        if (root == null) {
            return;
        }
        sb.append(root.val).append(SEP);
        // 处理孩子节点
        if (root.children != null) {
            // 给孩子节点用括号围起来
            sb.append(START_TAG).append(SEP);
            for (Node child : root.children) {
                dfs(child, sb);
            }
            sb.append(END_TAG).append(SEP);
        }
    }

    // Decodes your encoded data to tree.
    public Node deserialize(String data) {
        if (data == null) {
            return null;
        }
        String[] elements = data.split(SEP);
        Stack<Node> stack = new Stack<Node>();
        Node root = null;
        Node cur = null;
        for (String ele : elements) {
            if (ele.equals(START_TAG)) {
                stack.push(cur);
            } else if (ele.equals(END_TAG)) {
                stack.pop();
            } else {
                Node node = new Node(Integer.parseInt(ele));
                node.children = new ArrayList<>();
                if (root == null) {
                    root = node;
                } else {
                    Node parent = stack.peek();
                    parent.children.add(node);

                }
                cur = node;
            }
        }
        return root;
    }
}

八、其他

8.1 平衡树

序号问题描述完成
110. 平衡二叉树

110. 平衡二叉树

其实还是遍历。

class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        int left = getHeight(root.left);
        int right = getHeight(root.right);
        // 1、左子树和右子树的高度差不超过1
        // 2、左右子树自身也是平衡二叉树
        return Math.abs(left - right) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }

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

8.2 二叉搜索树

二叉搜索树是一个很重要的数据结构,加上自平衡特性就形成了红黑树等各种数据结构,需要重点掌握。

移步:二叉搜索树

8.3 字典树&前缀树

序号问题描述完成
14. 最长公共前缀
139. 单词拆分
140. 单词拆分 II
208. 实现 Trie (前缀树)

8.4 完全二叉树

序号问题描述完成
222. 完全二叉树的节点个数
class Solution {
    public int countNodes(TreeNode root) {
        TreeNode l = root, r = root;
        int hl = 0, hr = 0;
        // 计算左右子树的高度
        while (l != null) {
            l = l.left;
            hl++;
        }
        while (r != null) {
            r = r.right;
            hr++;
        }
        // 如果高度相等,那么说明是完全二叉树
        if (hl == hr) {
            return (int) Math.pow(2, hl) - 1;
        }
        // 如果高度不等,还是要手动计算
        return 1 + countNodes(root.left) + countNodes(root.right);
    }
}

8.5 线段树

序号问题描述完成
53. 最大子数组和

53. 最大子数组和

class Solution {
    public class Status {
        public int lSum, rSum, mSum, iSum;

        public Status(int lSum, int rSum, int mSum, int iSum) {
            this.lSum = lSum;
            this.rSum = rSum;
            this.mSum = mSum;
            this.iSum = iSum;
        }
    }

    public int maxSubArray(int[] nums) {
        return getInfo(nums, 0, nums.length - 1).mSum;
    }

    public Status getInfo(int[] a, int l, int r) {
        // 元素自身,每一个属性都是自己
        if (l == r) {
            return new Status(a[l], a[l], a[l], a[l]);
        }
        // 分治
        int m = (l + r) >> 1;
        Status lSub = getInfo(a, l, m);
        Status rSub = getInfo(a, m + 1, r);
        return pushUp(lSub, rSub);
    }

    public Status pushUp(Status l, Status r) {
        // 区间和
        int iSum = l.iSum + r.iSum;
        // 以l开头的最大区间和
        int lSum = Math.max(l.lSum, l.iSum + r.lSum);
        // 以r结尾的最大区间和
        int rSum = Math.max(r.rSum, r.iSum + l.rSum);
        // 最大区间和
        int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
        return new Status(lSum, rSum, mSum, iSum);
    }
}