经典数据结构题目-树

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

  • 思路

    • 先序遍历中,树的根节点放在第一位,后面是左右子树。中序遍历中,树的根节点放在中间,两边分为左右子树

    • 可基于以上规则区分出每棵树在数组中的区间

      • 先从先序数组中拿到根节点的val
      • 因为val不重复,定位到该树在中序数组的位置
      • 中序数组根节点位置确定,可以计算左右子树长度
      • 根据左右子树长度,可以定位出在先序数组中的位置
    • 能区分出树的区间,可用递归构建出树

      • 终止条件。区间内没有元素,无法构造出树
      • 当前层。构建根节点,计算左右子树区间,递归调用构造左右子树
      • 递归函数。入参为 树的先序数组、中序数组;出参为子树的根节点
  • 代码

    java 复制代码
        // pre 中 根节点在第一位,in 中 根节点的左边为 左子树,右边为右子树。可根据此规则区分出树的范围
        // 使用递归不断缩减树的范围,当没有子节点时则可以构造节点返回
        private Map<Integer,Integer> treeMap = new HashMap<>();
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            for(int i = 0; i < inorder.length; i ++){
                treeMap.put(inorder[i],i);
            }
            return buildNode(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
        }
    
        public TreeNode buildNode(int[] preorder,int preBegin,int preEnd,int[] inorder,int inBegin,int inEnd){
            // 终止条件
            if(preBegin > preEnd && inBegin > inEnd){
                return null;
            }
            // 当前层处理
            int intVal = preorder[preBegin];
            TreeNode root = new TreeNode(intVal);
            int inIndex = treeMap.get(intVal);
            int leftLength = inIndex - inBegin;
            // 递归调用
            root.left = buildNode(preorder,preBegin+1,preBegin+leftLength,inorder,inBegin,inIndex-1); //注意点一
            root.right = buildNode(preorder,preBegin+leftLength+1,preEnd,inorder,inIndex+1,inEnd);
            return root;
        }
  • 注意点

    • 注意点一,统一入参中索引构建的是左闭右闭区间 [begin,end]

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

  • 思路

    • 利用树的层序遍历。可获取到每一层的全部节点,修改next指针进行指向即可
  • 代码

    java 复制代码
        // 层序遍历。可单独获取到每一层的节点,遍历每一层的节点填充next属性即可
        public Node connect(Node root) {
            if(root == null) return null;
            Queue<Node> queue = new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                int size = queue.size();
                Node last = null;
                // 每一层的处理
                while(size-- > 0){
                    Node curr = queue.poll();
                    if(curr.left != null){
                        queue.offer(curr.left);
                    }
                    if(curr.right != null){
                        queue.offer(curr.right);
                    }
                    // 非每一行的首个节点
                    if(last != null){ 
                        last.next = curr;
                    }
                    last = curr;
                }
                
            }
            return root;
        }

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

  • 解题思路
    • root为p、q的最近公共祖先,可能为以下情况
      • p、q分别在root的左右子树
      • root等于p,q在root的左/右子树
      • root等于q,p在root的左/右子树
    • 用先序遍历递归来判断每个节点是否为最近公共祖先
      • 终止条件
        • root为null / p / q 时,root节点可能成为最近祖先,依赖root相同父节点的另外一个子节点的判断情况决定
      • 递归函数
        • 定义为,找到当前树中,可能存在最近祖先的节点
      • 当前层处理
        • 递归当前节点的左右子树,单一侧找不到 有可能成为祖先的,则另外一的成为祖先
  • 代码
java 复制代码
    // 先序遍历。找到空节点/val=p/val=q即可返回
    // 当左子树找到其中一个,另外一个为空,则说明只能出现在左子树/父节点
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root.val == p.val || root.val == q.val){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q); // 注意点一
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left == null){
            return right;
        }
        if(right == null){
            return left;
        }
        return root;
    }
  • 注意点
    • lowestCommonAncestor方法的定义发生了改变,不是找到最佳祖先,而且可能成为的祖先

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

  • 思路

    • 根节点到当前节点的数字 = 根到父节点的数 * 10 + 当前节点的数
    • 利用深度优先搜索,搜索到叶子节点,计算数字进行相加
  • 代码

java 复制代码
    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);
        }
    }
}

前中后序遍历

递归写法

  • 思路
    • 以先序为例,调整处理中左右节点的顺序即可
java 复制代码
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

    public void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
      	// 处理中节点
        result.add(root.val);
       // 处理左子节点
        preorder(root.left, result);
       // 处理右子节点
        preorder(root.right, result);
    }

迭代写法

  • 思路

    • 用压栈来模拟递归方法的调用。调用递归方法就将就是将该节点压入栈,元素再弹出时再对节点进行当前层的处理
    • 因为栈为后入先出,当前层的处理中,遍历的顺序为左中右,压栈的处理顺序就得为右中左
  • 代码

java 复制代码
// 中后序  
public List<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stk = new Stack<>();
        stk.push(root);
        while(!stk.isEmpty()){
            TreeNode curr = stk.pop(); 
            if(curr == null){
                res.add(stk.pop().val);
                continue;
            }
            // 右
            if(curr.right != null){
                stk.push(curr.right);
            }
            // 中
            stk.push(curr);
            stk.push(null); // 注意点一
            // 左
            if(curr.left != null){
                stk.push(curr.left);
            }
        }
        return res;
    }
// 先序
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null){
            return res;
        }
        Stack<TreeNode> stk = new Stack<>();
        stk.push(root);
        while(!stk.isEmpty()){
            // 当前节点可以直接处理
            TreeNode curr = stk.pop();
            res.add(curr.val);
            // 需要递归处理的变量,倒序放入到栈
            if(curr.right != null){
                stk.push(curr.right);
            }
            
            if(curr.left != null){
                stk.push(curr.left);
            }
        }
        return res;

    }
  • 注意
    • 注意点一。当前层处理中将子节点压栈的同时,需要再次将根节点入栈,并添加null节点作为标记。

530. 二叉搜索树的最小绝对差

  • 思路
    • 二叉搜索树的中序遍历就是一个有序数组
    • 根据以上,可以在中序遍历的过程中,保存上一个节点,并计算和上个节点的差
  • 代码
java 复制代码
  public int getMinimumDifference(TreeNode root) {
        int res = Integer.MAX_VALUE;
        Stack<TreeNode> stk = new Stack<>();
        stk.push(root);
        TreeNode pre = null;
        while(!stk.isEmpty()){
            TreeNode curr = stk.pop();
            if(curr == null){
                curr = stk.pop();
                // 遍历过程计算上一个的差
                if(pre != null){
                    res = Math.min(Math.abs(curr.val - pre.val),res);
                }
                pre = curr;
                continue;
            }
            // 右
            if(curr.right != null){
                stk.push(curr.right);
            }
            // 中
            stk.push(curr);
            stk.push(null);
            // 左
            if(curr.left != null){
                stk.push(curr.left);
            }
        }
        return res;
    }