饭后小甜点leetcode——通过前中后序遍历中的两种构造二叉树


这个系列总结一下,给定前/中/后序遍历其中两个遍历的结果,要求构造出原二叉树,这种题。
主要思路是用递归+分治

普通二叉树

前序+中序

leetcode题目地址
有以下两种方法

  1. 传递inOrder的左右边界和preOrder的offset。offset是指从preOrder获取到的当前子树的根的下标(preOrder是先遍历根节点,所以对于每一个子树,对应preOrder的序列的第一个元素就是根节点)
    【易错点】offset计算要注意一下,别算错了。各个变量的含义要始终明确。
    【时间复杂度】O(nlgn)
public class Solution {
    public TreeNode BuildTree(int[] preorder, int[] inorder) {
        return Build(preorder, inorder, 0, inorder.Length-1, 0);
    }
    
    //left, right: belong inorder
    public TreeNode Build(int[] preorder, int[] inorder, int left, int right, int offset){
        if(left>right||offset>preorder.Length) return null;
        var root = new TreeNode(preorder[offset]);
        //2. find
        var index = 0;
        for(var i=left;i<=right;i++){
            if(inorder[i]==root.val){
                index = i;
                break;
            }
        }
        //3.connect left and right
        root.left = Build(preorder, inorder, left, index-1, offset+1);
        root.right = Build(preorder, inorder, index+1, right, offset+1+index-left);
        return root;
    }
}
  1. 用字典把元素在inorder里面的下标记录下来(预处理),offset通过规律发现直接自增就work。
    【巧妙的点】用字典记录下标,preOrder的第一个元素是根节点,找这个根节点在inOrder中的位置的时候就O(1)复杂度。而且如果按照类似先序的解决问题的递归,在preOrder中找根节点的时候,就类似和preOrder同步了,所以根节点下标可以通过全局变量自增。这种方法至少在C#的代码速度分布图中是最快的那个。
    【易错点】易错点就在于。。。这种巧妙地方法要记住哇
    【复杂度】O(n)
public class Solution {
    int offset = 0;
    Dictionary<int, int> dict = new Dictionary<int, int>();
    public TreeNode BuildTree(int[] preorder, int[] inorder)
    {
        for (var i=0;i<inorder.Length;i++)
        {
            dict[inorder[i]] = i;
        }

        return helper(preorder, inorder, 0, inorder.Length);
    }

    private TreeNode helper(int[] preorder, int[] inorder, int left, int right)
    {
        if (left == right)
            return null;
            
        var rootVal = preorder[offset];
        var root = new TreeNode(rootVal);
        var index = dict[rootVal];
        offset++;
        
        root.left = helper(preorder, inorder, left, index);
        root.right = helper(preorder, inorder, index + 1, right);
        return root;
    }
}

对比后,发现第二种方法会快一点,所以后面的两种题都用第二种方法。

中序+后序

leetcode题目地址
【思路】沿用使用dictionary存储元素下标的思想。
【要注意的点】现在的offset是postOrder中的,所以要想直接通过offset递减来获取每次子树的根,那么,在递归的时候,要先递归右子树,再递归左子树

public class ConstructTreeFromPostIn
{
    int offset = 0;
    Dictionary<int, int> dict = new Dictionary<int, int>();
    public TreeNode BuildTree(int[] inorder, int[] postorder)
    {
        for (var i = 0; i < inorder.Length; i++)
        {
            dict[inorder[i]] = i;
        }
        offset = postorder.Length - 1;

        return helper(postorder, inorder, 0, inorder.Length);
    }

    private TreeNode helper(int[] postorder, int[] inorder, int left, int right)
    {
        if (left == right)
            return null;
        var rootVal = postorder[offset];
        var root = new TreeNode(rootVal);

        var index = dict[rootVal];

        offset--;

        root.right = helper(postorder, inorder, index + 1, right);
        root.left = helper(postorder, inorder, left, index);

        return root;
    }
}

前序+后序

leetcode题目地址
【思路】依然沿用使用dictionary存储元素下标的思想。只不过在这道题中,不但要存储preorder的下标,还要存postorder的下标,因为在后面的递归函数中,我们需要先通过preorder的当前根节点后第一个元素找到该元素在postorder中的位置,再通过postorder当前根节点之前最后一个元素找到该元素在preorder中的位置。
【不同】在这道题中,递归函数中的left和right参数,指的是preorder中的边界,在上两道题中都是指inorder的边界

public class ConstructTreeFromPrePost
{
    Dictionary<int, int> preDict = new Dictionary<int, int>();
    Dictionary<int, int> postDict = new Dictionary<int, int>();
    public TreeNode ConstructFromPrePost(int[] preorder, int[] postorder)
    {
        for (var i = 0; i < preorder.Length; i++)
            preDict[preorder[i]] = i;

        for (var i = 0; i < postorder.Length; i++)
            postDict[postorder[i]] = i;

        return helper(preorder, postorder, 0, postorder.Length - 1);
    }

    public TreeNode helper(int[] preorder, int[] postorder, int left, int right)
    {
        //Console.WriteLine(left+"  "+right+"  ");
        if (left > right)
            return null;

        var root = new TreeNode(preorder[left]);

        if (left == right)
            return root;

        var r = postDict[root.val] - 1;
        if (r < 0 || r >= postorder.Length) return root;
        var l = preDict[postorder[r]];

        root.left = helper(preorder, postorder, left + 1, l - 1);
        root.right = helper(preorder, postorder, l, right);

        return root;
    }
}

BST

前序

leetcode题目地址
【思路】还是类似上面普通的二叉树的构造,但BST有很好的特性,左边所有节点大于root,右边所有节点小于root,所以解决这道题的话,就先把第一个元素拿出来当root,然后找它后面比它大的元素的位置,然后后面的元素就分成了两半,可以进行递归。
left和right是指当前递归到的子树在数组中的边界。
【小Tip】可以用Binary Search找后面第一个比root大的元素的位置,如果在给定范围内找不到,就返回右边界加一,也就是该元素在给定范围情况下应该插入的位置

public class ConstructBstFromPreorder
{
    public TreeNode BstFromPreorder(int[] preorder)
    {
        return helper(preorder, 0, preorder.Length - 1);
    }

    public TreeNode helper(int[] preorder, int left, int right)
    {
        if (left > right) return null;

        var root = new TreeNode(preorder[left]);
        if (left == right) return root;

        var k = Find(preorder, root.val, left + 1, right);

        root.left = helper(preorder, left + 1, k - 1);
        root.right = helper(preorder, k, right);
        return root;
    }

    public int Find(int[] arr, int target, int lo, int hi)
    {
        if (lo < 0 || lo >= arr.Length) return lo;
        while (lo < hi)
        {
            int mid = lo + (hi - lo) / 2;
            if (target <= arr[mid])
            {
                hi = mid;
            }
            else
            {
                lo = mid + 1;
            }
        }
        if (arr[lo] >= target)
        {
            return lo;
        }
        else
        {
            return hi + 1;
        }
    }
}

中序

leetcode题目地址
【思路】BST的中序遍历就是一个有序数组,所以从中序构造BST的话就有好多种构造方法,但一般就用最简单最整齐的,每次取中间值作为root的方法。这道题应该是这几种里面比较简单的,就是注意一下记一下思路就行。

public class ConvertSortedArrayToBST
{
    public TreeNode SortedArrayToBST(int[] nums)
    {
        return ToBST(nums, 0, nums.Length - 1);
    }

    public TreeNode ToBST(int[] nums, int start, int end)
    {
        if (start > end) return null;
        var mid = (start + end) / 2;
        var root = new TreeNode(nums[mid]);
        root.left = ToBST(nums, start, mid - 1);
        root.right = ToBST(nums, mid + 1, end);
        return root;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值