二叉树的面试题

来源:互联网 发布:3米直尺道路检测数据 编辑:程序博客网 时间:2024/06/06 00:35

1. 求两个节点的最近公共祖先节点

这里写图片描述

假设现在找的是节点X1和X2的最近的公共祖先节点。
这里由于这棵树的结构或者说是特点从而导致这颗树的解法会有多种思路:

第一种就是这棵二叉树是一个搜索二叉树,那么可以根据搜索二叉树的特点从而找出两个节点的最近的公共祖先节点。
可以想到,如果X1在一个节点的左子树,X2在一个节点的右子树,或者X1在一个节点的右子树,而X2在一个节点的左子树,这样就可以说明该节点一定就是X1和X2的最近的公共祖先节点。
这里应该注意一个小问题:就是图中2和3的最近的公共祖先节点应该是3.

简单描述下这个过程:
如果X1这个节点比遍历到的当前节点大,则说明X1这个节点肯定在当前节点的左子树中,如果X1这个节点比遍历到的当前节点小,则说明X1这个节点一定在当前节点的右子树中,然后就是只要可以判断出节点X1和节点X2分别位于一个节点的左子树和右子树中,那么就可以返回这个节点(这个节点即就是我们所求的X1和X2的最近的公共祖先节点)。
使用递归就很容易写出代码。这种方法的时间复杂度是o(N)。

第二种是如果这棵二叉树的结构是一种三叉链的结构,那么可以从X1节点开始,直至根节点,将这条路径保存在一个数组里,然后再从X2节点开始遍历至根节点,将这条路径保存在另一个数组里。然后用其中一个数组中的第一个节点开始去另一个数组中查找,如果找到相同的,则就说明该节点就是这两个节点的公共祖先节点,如果没有找到相同的,则继续用第二个节点用同样的方法遍历另一个数组,直至找到相同的节点。
但是这种方法的时间复杂度是O(N^2)。

第三种就是上面图中最为普通的二叉树,这种普通二叉树去查找任意两个节点的最近的公共祖先节点思路可以参考一下上面二叉搜索树的方法,利用一个Find的接口函数,在一个节点的左子树去查找X1,如果找不到,再去该节点的右子树去查找,将查找的结果记录下来,再利用Find的接口函数去右子树查找X2,如果找不到,则去该节点个左子树去查找。最后利用他们查找的结果来判断这两个节点的最近的公共祖先节点。
这种思路的时间复杂度依然是O(N^2)。

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };    typedef BinaryTreeNode<T> Node;    BinaryTree()        :_root(NULL)    {}    bool Find(Node* x)    {        assert(x);        return _Find(_root,x);    }    //时间复杂度是O(N^2)    Node* GetLastCommonAncestor(Node* x1, Node* x2)    {        assert(x1 && x2);        if (_root == NULL)        {            return NULL;        }        return _GetLastCommonAncestor(_root, x1, x2);    }private:    Node* _Find(Node* root, const T& x)    {        if (root == NULL)        {            return NULL;        }        if (root->_val == x)        {            return root;        }        Node* l = _Find(root->_left, x);        if (l == NULL)        {            return _Find(root->_right, x);        }       }    Node* _GetLastCommonAncestor(Node* root, Node* x1, Node* x2)    {        //在子树中        if (root == x1 || _root == x2)        {            return root;        }        bool x1InLeft = false, x1InRight = false, x2InLeft = false, x2InRight = false;        x1InLeft = _Find(root->_left, x1);        if (x1InLeft == false)        {            x1InRight = _Find(root->_right, x1);        }        x2InLeft = _Find(root->_left, x2);        if (x2InLeft == false)            x2InRight = _Find(root->_right, x2);        if (x1InLeft && x2InRight || x1InRight && x2InLeft)//一个节点在左子树,一个节点在右子树        {            return root;        }        else if (x1InLeft && x2InLeft)//两个节点都在左子树        {            return _GetLastCommonAncestor(root->_left, x1, x2);        }        else if (x1InRight && x2InRight)//两个节点都在右子树        {            return _GetLastCommonAncestor(root->_right, x1, x2);        }    }    bool _Find(Node* root, Node* x)    {        if (root == NULL)        {            return false;        }        if (root == x)        {            return true;        }        bool l = _Find(root->_left, x);        if (l)            return true;        return _Find(root->_right, x);    }    Node* _root;};

不过这种思路还是可以优化的,逻辑思路类似于上面三叉链的方法。
就是在找一个节点时,就将查找的路径直接保存到一个栈里。
另一个节点采用同样的方法,将查找的路径保存到另一个栈里。
然后需要保证这两个栈中节点的个数是相等的,很简单,让节点个数大的出几次栈就好,直至两个栈中节点的个数是相等的。然后取出两个栈的栈顶元素,如果相等,则该栈顶元素就是我们所求的最近的公共祖先节点,如果不等,那么就让两个栈同时出栈,接着比较。
这种思路的时间复杂度是O(N)。

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };    typedef BinaryTreeNode<T> Node;    BinaryTree()        :_root(NULL)    {}    bool Find(Node* x)    {        assert(x);        return _Find(_root,x);    }        //时间复杂度为O(N)    Node* GetLastCommonAncestor(Node* x1, Node* x2)    {        assert(x1 && x2);        stack<Node*> x1paths;        stack<Node*> x2paths;        Find(x1, x1paths);        Find(x2, x2paths);        while (x1paths.size() != x2paths.size())        {            if (x1paths.size() > x2paths.size())            {                x1paths.pop();            }            else            {                x2paths.pop();            }        }        while (!x1paths.empty() && !x2paths.empty() && x1paths.top() != x2paths.top())        {            x1paths.pop();            x2paths.pop();        }        if (x1paths.top() == x2paths.top())        {            return x1paths.top();        }        return NULL;    }    bool Find(Node* x, stack<Node*>& paths)    {        return _Find(_root, x, paths);    }private:    bool _Find(Node* root, Node* x, stack<Node*>& paths)    {        if (root == NULL)        {            return false;        }        paths.push(root);        if (root == x)        {            return true;        }        //说明:只要找到就可以直接返回了        bool l = _Find(root->_left, x, paths);        if (l == true)        {            return true;        }        bool r = _Find(root->_right, x, paths);        if (r == true)        {            return true;        }            paths.pop();//到这里就说明在一个节点的左右子树中都没有找到,那么就可以pop出该节点,给递归程序的上层返回false            return false;    }    bool _Find(Node* root, Node* x)    {        if (root == NULL)        {            return false;        }        if (root == x)        {            return true;        }        bool l = _Find(root->_left, x);        if (l)            return true;        return _Find(root->_right, x);    }    Node* _root;};

2. 判断一棵二叉树是否是平衡二叉树【y】

平衡二叉树,即就是要求一棵二叉树的左右高度差是否小于等于1。

思路参考:
第三题
时间复杂度为O(N^2)
先判断当前节点是否为平衡二叉树就需要遍历整棵二叉树,求得左右子树的高度,然后递归判断左子树是否为平衡二叉树,又需要遍历左子树求得左子树的高度,判断右子树是否为平衡二叉树,需要遍历右子树求得右子树的高度,这里面会有很多的冗余值。将越接近叶子节点的节点的高度求了很多次,这都是没有必要的,下面会将其优化。

//时间复杂度为O(N^2)    bool _IsBalance(Node* root)    {        if (root == NULL)        {            return true;        }        int l = _Height(root->_left);        int r = _Height(root->_right);        //到这里说明已经是二叉树了        return abs(l - r) < 2            && _IsBalance(root->_left)        && _IsBalance(root->_right);    }size_t _Height(Node* root)    {        if (root == NULL)        {            return 0;        }        size_t leftlen = 0, rightlen = 0;        if (root->_left)         leftlen = _Height(root->_left);        if (root->_right)         rightlen = _Height(root->_right);        return leftlen > rightlen ? leftlen + 1 : rightlen + 1;    }

时间复杂度为O(N)

利用函数的参数列表将每一个节点的高度带回上层递归程序中。
也就是从叶子节点开始判断该棵子树是否为平衡二叉树。

//时间复杂度为O(N)    bool _IsBalance(Node* root, int& depth)    {        if (root == NULL)        {            depth = 0;            return true;        }        int ldepth = 0, rdepth = 0;        if (_IsBalance(root->_left, ldepth) == false)        {            return false;        }        if (_IsBalance(root->_right, rdepth) == false)        {            return false;        }        if (abs(ldepth - rdepth) > 1)        {            return false;        }        depth = ldepth > rdepth ? ldepth + 1 : rdepth + 1;//这棵子树已经是平衡二叉树了        return true;    }

3. 求二叉树中最远的两个节点的距离

在遇到这个问题的时候,一开始我想的就是使用递归,我们先来考虑这样一个问题,如何判断一个子树中最远两个节点的距离?是不是只要把一个子树的左子树和右子树的高度分别求出来,然后将这两个值相加就是该子树最远两个节点的距离。但是需要不断更新最远距离,因为当前树中的最远的两个 节点的距离不一定就是该二叉树中最远两个节点的距离,就用上图来说明这个问题吧

这里写图片描述

根节点为6的这棵二叉树的最远距离就是6.
而根节点为5的这棵二叉树的最远距离就是8,所以并不是根节点为6的这棵子树所求出的最远距离就是这棵二叉树真正的最远距离。

求最远距离时需要求每个子树的左右高度,第一方法是按照正常思路从根节点开始求出根节点的左右高度,然后将左右高度相加得到第一个maxlen(最远距离)的值,随后就是不断递归左子树向下查找每个节点的左右高度,然后更新maxlen的值,左子树遍历到叶子节点时就可以回溯了,回溯到达根节点然后不断递归右子树求出每个节点的左右高度然后更新maxlen的值,最终回溯回根节点后就求出了maxlen的值。

但是这个过程有很多的冗余值,就是在求根节点6的左右高度时,其实已经把左子树和右子树中每一个节点的高度求过一次了,一次求节点的高度,假设左右子树每次平分,时间复杂度就是O(N),而需要递归求每个节点的左右子树高度,遍历每个节点,这个时间复杂度又是O(N),递归的时间复杂度就是递归的总次数*每次递归的次数。所以这种思路的时间复杂度就是O(N^2),这种思路的效率是不是太低了呢。

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };    typedef BinaryTreeNode<T> Node;    BinaryTree()        :_root(NULL)    {}//时间复杂度:O(N^2)    void FindMaxLen(size_t& Maxlen)//不断更新最远距离    {        _FindMaxLen(_root, Maxlen);    }private:void _FindMaxLen(Node* root, size_t& Maxlen)    {        size_t leftlen = 0, rightlen = 0;        if (root->_left)            leftlen = _Height(root->_left);        if (root->_right)            rightlen = _Height(root->_right);        if (leftlen + rightlen > Maxlen)        {            Maxlen = leftlen + rightlen;        }        if (root->_left)            _FindMaxLen(root->_left, Maxlen);        if (root->_right)            _FindMaxLen(root->_right, Maxlen);    }size_t _Height(Node* root)    {        if (root == NULL)        {            return 0;        }        size_t leftlen = 0, rightlen = 0;        if (root->_left)         leftlen = _Height(root->_left);        if (root->_right)         rightlen = _Height(root->_right);        return leftlen > rightlen ? leftlen + 1 : rightlen + 1;    }    Node* _root;};

我们来思考一下上面的思路可不可以优化呢?
如果我们想从叶子节点4开始求高度,然后将4的高度返回给4的父亲8,接着再求8的左右子树高度(利用4返回的高度)不需要再重新求4的高度,而且只要求出每个节点的左右子树高度就可以更新该节点的最远距离,那么要完成这样的逻辑其实使用递归就可以,当求根节点6的最远距离时先不要求6节点的最远距离,而是先去求它的左子树和右子树的最远距离,这样子不断递归,最终先求出最远距离的肯定会是叶子节点,然后将每个节点的高度都返回给该节点的父亲。这样等于遍历一遍就可以求出整棵二叉树的最远距离。所以时间复杂度是O(N)。

这道面试题也考虑到了多线程的问题,因为这个maxlen我们需要不断的更新,所以可能会有很多人考虑使用全局变量,但是使用全局变量,我们就需要考虑到在多线程环境下,要使得线程安全的话,可能需要加锁,为了简化问题,既然全局变量会引起线程安全问题,那么就不要使用全局变量,而是使用局部变量,我们都知道,每个线程都会有自己的私有栈空间,那么每个线程看见的maxlen就不是同一个,这样就使得该函数是一个可重入函数。

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };    typedef BinaryTreeNode<T> Node;    BinaryTree()        :_root(NULL)    {}//时间复杂度是O(N)    size_t FindMaxLen()//不断更新最远距离    {        //这里的Maxlen考虑到线程安全的问题,也就是使得下面调用的函数是可重入的(所有将Maxlen给成临时变量而不是全局变量)        size_t MaxLen = 0;        _FindMaxLen(_root, MaxLen);        return MaxLen;    }private:    size_t _FindMaxLen(Node* root, size_t& Maxlen)    {        if (root->_left)            _FindMaxLen(root->_left, Maxlen);        if (root->_right)            _FindMaxLen(root->_right, Maxlen);        size_t leftlen = 0, rightlen = 0;            leftlen = _Height(root->_left);            rightlen = _Height(root->_right);        if (leftlen + rightlen > Maxlen)        {            Maxlen = leftlen + rightlen;        }        return leftlen > rightlen ? leftlen + 1 : rightlen + 1;    }    size_t _Height(Node* root)    {        if (root == NULL)        {            return 0;        }        size_t leftlen = 0, rightlen = 0;        if (root->_left)         leftlen = _Height(root->_left);        if (root->_right)         rightlen = _Height(root->_right);        return leftlen > rightlen ? leftlen + 1 : rightlen + 1;    }    Node* _root;};

4. 由前序遍历和中序遍历重建二叉树(前序序列:1 2 3 4 5 6 - 中序序列:3 2 4 1 6 5)

这里写图片描述

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };    typedef BinaryTreeNode<T> Node;    BinaryTree()        :_root(NULL)    {}Node* ReBulidTree(char* pre, char* In, size_t len)    {        if (pre == NULL || In == NULL || len <= 0)        {            return NULL;        }        return _ReBulidTree(pre, In, In + len);//两段区间都是闭区间;    }private:    Node* _ReBulidTree(char*& prev,char* In,char* InEnd)    {        if (*prev == '\0')//说明子二叉树已经创建成功了        {            return NULL;        }        Node* newRoot = new Node(*prev);//有两种情况:1.是叶子节点 2.不是叶子节点        //1.是叶子节点        if (In == InEnd)        {            return newRoot;        }        //2.不是叶子节点,这时就可以确定左右子树的区间了        char* pos = In;        while (pos != InEnd)        {            if (*pos == *prev)            {                break;//说明已经找到根节点了            }            pos++;        }        //这里的pos一定不能等于InEnd        assert(pos <= InEnd);        //左右子树可以利用递归去解决        newRoot->_left = _ReBulidTree(++prev,In, pos - 1);        newRoot->_right = _ReBulidTree(++prev,pos + 1, InEnd);        return newRoot;    }    Node* _root;};

最近做剑指offer上的这道题时,发现了一种逻辑更清晰的做法:

#include <algorithm>class Solution {public:    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin)    {        if(pre.empty())            {            return NULL;        }        TreeNode* newRoot = new TreeNode(pre[0]);        vector<int>::iterator it;        it =find(vin.begin(),vin.end(),pre[0]);        int leftNum = 0;//统 计左子树中节点的个数        if(it != vin.end())            {            leftNum = it - vin.begin();        }else            {            return NULL;        }        //pre(begin+ 1,begin + 1 + leftNum)--》去掉头结点(第一个节点)    vin(begin,begin + leftNum)         //pre(begin + 1 + leftNum,end);   in(begin + 1 + leftNum,end)//去掉头结点,头结点在中间        newRoot->left = reConstructBinaryTree(vector<int> (pre.begin() + 1,pre.begin() + leftNum + 1),vector<int>(vin.begin(),vin.begin() + leftNum));        newRoot->right = reConstructBinaryTree(vector<int>(pre.begin() + leftNum + 1,pre.end()),vector<int>(vin.begin() + leftNum + 1,vin.end()));        return newRoot;    }};

5.判断一棵树是否是完全二叉树

思路:

这里写图片描述

需要设定一个标记位来标记一个节点有没有孩子节点。

注意以下几点:
1.将每一个节点没有左孩子或右孩子归为一种情况,不要分开进行分析,分开的话容易搞混
2.只要有一个节点有孩子节点那么就判断标志位是否为false,如果为false,那么就说明该节点前面有节点没有左孩子或者右孩子,不符合完全二叉树的特定,这时就可以得出结论:不是完全二叉树,就不必向下继续遍历,如果是true的话,说明该节点前面的所有节点的左右孩子节点都存在,那就按照前面的方法继续向后判断。

bool IsCompleteBinaryTree(Node* _root)    {        queue<Node*>  q;        q.push(_root);        bool tag = true;        while (!q.empty())        {            Node* front = q.front();            q.pop();            if (front->_left)            {                if (tag == false)                {                    return false;                }                q.push(front->_left);            }            else            {                tag = false;            }            if (front->_right)            {                if (tag == false)                {                    return false;                }                q.push(front->_right);            }            else            {                tag = false;            }        }        return true;    }

6. 求二叉树的镜像【y】

这里写图片描述

这个问题用递归解决其实很简单,不断递归交换一个根节点的左右子树即可。

void  _Mirror(Node* root)    {        if (root == NULL)        {            return;        }        swap(root->_left, root->_right);        _Mirror(root->_left);        _Mirror(root->_right);    }

7.将二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

利用的是二叉搜索树的线索化。
left相当于是prev,right相当于是next。

template<class T>class BinaryTree{public:    template<class T>    struct BinaryTreeNode    {        BinaryTreeNode* _left;        BinaryTreeNode* _right;        T _val;        BinaryTreeNode(const T& val)            :_left(NULL)            , _right(NULL)            , _val(val)        {}        BinaryTreeNode()            :_left(NULL)            , _right(NULL)        {}    };typedef BinaryTreeNode<T> Node;BinaryTree()        :_root(NULL)    {}//转换为双向链表    Node* ToDuplexingList()    {        Node* prev = NULL;        _ToDuplexingList(_root,prev);        Node* cur = _root;        while (cur->_left)        {            cur = cur->_left;        }        return cur;//返回单链表的头    }private:    void _ToDuplexingList(Node* cur, Node*& prev)    {        if (cur == NULL)        {            return ;        }        _ToDuplexingList(cur->_left, prev);        cur->_left = prev;        if (prev)        {            prev->_right = cur;        }        prev = cur;        _ToDuplexingList(cur->_right, prev);    }    Node* _root;};
原创粉丝点击