多叉树上的操作

这里的树指的是有根树。

1. LC 105/106 从前/中序与中/后序遍历序列构造二叉树

大二数据结构实验做过类似的。105和106本质上是一样的,这里就以105为例好了。

我的思路比较直观朴素,首先既然是先序遍历,那么肯定第一个元素就是整棵树的根节点。而中序遍历是,先深搜左子树,然后访问当前节点,再深搜右子树。这就造成了中序遍历的格式一定是:

left subroot right

的格式,也就是子树根节点左侧都是这棵子树的左子树节点,右侧都是右子树节点。

那么我们就可以在中序遍历中根据根节点将其一分为二,构造左子树与右子树。

那么子树的根是什么呢?我们注意到先序遍历中,一定是某棵子树的根节点访问的时机一定先于这棵子树中的其他节点。所以对于中序遍历中的某个区间[l,r],我们只需要找到该区间中在先序遍历中索引最小的元素,将其作为根节点即可。这就需要维护各个元素在先序遍历中的索引,以便维护一个最小值。

import java.util.HashMap;
import java.util.Map;

class Solution {
    Map<Integer,Integer> m;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        m = new HashMap<>();
        for (int i = 0; i < preorder.length; i++) {
            m.put(preorder[i],i);
        }
        int r = preorder[0];

        int ri = 0;
        for (int i = 0; i < inorder.length; i++) {
            if(inorder[i]==r){
                ri = i;
                break;
            }
        }

        TreeNode root = new TreeNode(r);
        root.left = dfs(inorder,0,ri-1);
        root.right = dfs(inorder,ri+1,inorder.length-1);
        return root;
    }

    private TreeNode dfs(int[] inOrder,int l,int r){
        if(l>r){
            return null;
        }

        int mid = inOrder.length;
        int min = inOrder.length;
        for(int i=l;i<=r;i++){
            int preIndex = m.get(inOrder[i]);
            if(preIndex<min){
                mid = i;
                min = preIndex;
            }
        }

        TreeNode subRoot = new TreeNode(inOrder[mid]);
        subRoot.left = dfs(inOrder,l,mid-1);
        subRoot.right = dfs(inOrder,mid+1,r);

        return subRoot;
    }

那么复杂度是多少呢?在构建每棵子树时,我们都要搜索[l,r]区间中的最小索引,而查询索引是通过hash表的,最好情况下是O(n),最坏情况下是O(n),总共n次查询(假设n个节点),那么一棵子树的复杂度最好是O(n),最坏就是O(n²)。总共n个节点,也就是n棵子树,所以最好O(n²),最坏O(n³)。

后来我看了其他提交的解答,看到一种不用玩索引的,而且也比较直观的,且复杂度也比较好的写法(玩索引指的是根据性质找到规律,直接把构建子树的区间按公式写死在代码里面,这种做法的好处是代码跑得快 ,但是缺点是不够直观,写起来恶心)

首先和上面说的一样,先序遍历中,索引越小,level越高。所以我们可以直接遍历整个先序遍历数组,每次遍历到的就是当前需要构建子树的根节点。随后并不记录先序遍历的索引,而是记录中序遍历的索引,查询每次构建子树的根节点在中序遍历中的索引idx,然后把中序遍历的区间[l,r]分为[l,idx-1]和[idx+1,r]两部分,继续递归地构建子树即可。

import java.util.HashMap;
import java.util.Map;

class Solution {
    Map<Integer,Integer> m;
    int[] pre;
    int[] in;
    int pre_idx;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        m = new HashMap<>();
        pre = preorder;
        in = inorder;
        pre_idx = 0;

        for (int i = 0; i < in.length; i++) {
            m.put(in[i],i);
        }

        return dfs(0,in.length-1);
    }

    private TreeNode dfs(int l,int r){
        if (l > r) {
            return null;
        }

        TreeNode subRoot = new TreeNode(pre[pre_idx]);
        Integer in_idx = m.get(pre[pre_idx]);
        pre_idx++;

        subRoot.left = dfs(l,in_idx-1);
        subRoot.right = dfs(in_idx+1,r);

        return subRoot;
    }

}

这里复杂度最好O(n),最坏O(n²),瓶颈在于哈希表查询速度。

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

这个题和LC 105/106基本上不是一回事儿了。但是思路差不多。

可以这么考虑,倒着遍历后序数组,把每个节点作为当前子树的根节点,并且是上一级的右子节点(因为后序先走右)。维护一个先序索引的哈希表。根据hash表分段。当前节点以及在先序遍历中位于其后的所有节点,显然都是右子树,而之前的显然都是左子树。

例如:

preorder = [1,2,4,5,3,6,7],
postorder = [4,5,2,6,7,3,1]

先遍历到1,建立一个根节点。随后在先序中,preorder[1,6]都是这个根节点的子树的节点。

然后遍历到3,建立一个右子树的根节点。随后在先序中,preorder[5,6]都是这个根节点的子树的节点。

那么1的右子树这样深搜下去有了。那么左子树呢?如果右子树存在,那么必然也存在左子树(因为是多个可行答案返回一个,所以我们不妨就这样认为)。而左子树就是[2,4,5]这三个节点,也就是在右子树根节点的先序遍历的索引前,且在父节点的先序遍历的索引后的这么一段区域。

假设子树的根节点的索引是i = pre_map[subRoot.val]。那么右子树(在先序遍历中)的区间就是[i+1,r],如果右子树存在,那么左子树的区间就是[i+1,pre_map[subRoot.right.val]-1]。

class Solution {
    int[] pre;
    int[] post;
    int[] pre_map;
    int post_idx;
    int n;
    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        pre_map = new int[31];
        pre = preorder;
        post = postorder;
        n = pre.length;

        for (int i = 0; i < preorder.length; i++) {
            pre_map[preorder[i]] = i;
        }

        post_idx = n-1;
        return dfs(0,n-1);
    }

    private TreeNode dfs(int l,int r){
        if(l>r){
            return null;
        }
        TreeNode subRoot = new TreeNode(post[post_idx]);
        int nl = pre_map[subRoot.val] + 1;

        post_idx--;
        subRoot.right = dfs(nl,r);
        if(subRoot.right!=null){
            subRoot.left = dfs(nl,pre_map[subRoot.right.val]-1);
        }

        return subRoot;
    }

}

3. LC 235 二叉搜索树的最近公共祖先

这道题需要注意到BST的一个性质,就是根节点左侧全小于等于根,右侧全大于等于根。

所以对于任意一个子树的节点,如果p和q的值都小于它,那么就往左子树搜就可以;都大于就右子树,否则这个节点就是LCA。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (true){
            if (root.val < p.val && root.val < q.val){root = root.right;}
            else if (root.val > p.val && root.val > q.val){root = root.left;}
            else {return root;}
        }
    }
}

4. LC 2673 使二叉树所有路径值相等的最小代价

贪心+深搜。每次把左右子树的差距平掉,然后返回目前最大的路径和即可。

class Solution {
    int ans;
    public int minIncrements(int n, int[] cost) {
        ans = 0;
        dfs(cost,1);
        return ans;
    }

    private int dfs(int[] cost,int i){
        if(i>cost.length){
            return 0;
        }

        int lSum = dfs(cost, 2 * i);
        int rSum = dfs(cost, 2 * i + 1);

        ans+=Math.abs(lSum-rSum);

        return Math.max(lSum,rSum)+cost[i-1];
    }
}

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值