剑指offer(四)树

面试题6:重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路:递归构建左右子树。

TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
    int length = pre.size();
    return constructCore(pre,vin,0,length-1,0,length-1);
}
TreeNode* constructCore(vector<int> pre,vector<int> vin, int preL, int preR, int inL, int inR){
    int rootValue = pre[preL];
    TreeNode* root = new TreeNode(rootValue);
    int rootInOrder = inL;
    while(rootInOrder <= inR && vin[rootInOrder] != rootValue)
        rootInOrder++;
    int leftLength = rootInOrder - inL;
    if(leftLength > 0){
        root->left = constructCore(pre,vin,preL+1,preL+leftLength,inL,rootInOrder-1);
    }
    if(leftLength < preR-preL){
        root->right = constructCore(pre,vin,preL+leftLength+1,preR,rootInOrder+1,inR);
    }
    return root;
}

面试题18:树的子结构

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解题思路:递归,但是要注意检查树是否为空。

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result = false;
        if(pRoot1 != NULL && pRoot2 != NULL){
            if(pRoot1->val == pRoot2->val)
                result = DoesTree1HasTree2(pRoot1, pRoot2);
            if(!result)
                result = HasSubtree(pRoot1->left, pRoot2);
            if(!result)
                result = HasSubtree(pRoot1->right, pRoot2);
        }
        return result;
    }
    bool DoesTree1HasTree2(TreeNode* pRoot1, TreeNode* pRoot2){
        if(pRoot2 == NULL)
            return true;
        if(pRoot1 == NULL)
            return false;
        if(pRoot1->val != pRoot2->val)
            return false;
        return DoesTree1HasTree2(pRoot1->left,pRoot2->left) && DoesTree1HasTree2(pRoot1->right,pRoot2->right);
    }
};

面试题19:二叉树的镜像

题目:操作给定的二叉树,将其变换为源二叉树的镜像。
解题思路:交换所有的左右子树,直至遍历完所有的非叶子结点。

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL)
            return;
        if(pRoot->left==NULL && pRoot->right==NULL)
            return;
        TreeNode *tmp = pRoot->right;
        pRoot->right = pRoot->left;
        pRoot->left = tmp;
        if(pRoot->left != NULL)
            Mirror(pRoot->left);
        if(pRoot->right != NULL)
            Mirror(pRoot->right);
    }
};

面试题23:从上到下打印二叉树

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路:队列

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> result;
        if(root == NULL)
            return result;
        queue<TreeNode*> treeQueue;
        treeQueue.push(root);
        while(!treeQueue.empty()){
            TreeNode *tmp = treeQueue.front();
            treeQueue.pop();
            result.push_back(tmp->val);
            if(tmp->left != NULL)
                treeQueue.push(tmp->left);
            if(tmp->right != NULL)
                treeQueue.push(tmp->right);
        }
        return result;
    }
};

面试题24:二叉搜索树的后序遍历序列

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
解题思路:后序遍历的话,最后一个数是根结点,因为是二叉搜索树,左子树都比根结点小,右子树都比根结点大。

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        return PostBST(sequence,0,sequence.size()-1);
    }
    bool PostBST(vector<int> sequence, int start, int end){
        if(end < start)
            return false;
        //int len = end - start + 1;
        int root = sequence[end]; //找到根结点
        int i = start;
        //在二叉搜索树中左子树的结点小于根结点
        for(;i<end;i++){
            if(sequence[i] > root)
                break;
        }
        int j = i;
        //在二叉搜索树中右子树的结点大于根结点
        for(;j<end;j++){
            if(sequence[j] < root)
                return false;
        }
        //判断左子树是不是二叉搜索树
        bool left = true;
        if(i>start)
            left = PostBST(sequence,start,i-1);
        //判断右子树是不是二叉搜索树
        bool right = true;
        if(i<end-1)
            right = PostBST(sequence,i,end-1);
        return (left && right);
    }
};

面试题25:二叉树中和为某一值的树

题目:输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
解题思路:画出遍历二叉树的过程,寻找规律:如果是叶节点,判断该路径上的和是否等于目标值;如果不是叶节点,则继续遍历它的孩子节点;路径的保存用栈,因为路径要与递归调用状态一致。

class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root != NULL){
            int currentSum = 0;
            vector<int> path;
            find(root, expectNumber, currentSum, path);
        }
        return result;
    }
    void find(TreeNode* root,int expectNumber,int currentSum,vector<int>& path){
        currentSum += root->val;
        path.push_back(root->val);
        bool isLeaf = (root->left == NULL && root->right == NULL);
        if(isLeaf && currentSum == expectNumber){
            result.push_back(path);
        }
        if(root->left != NULL)
            find(root->left, expectNumber, currentSum, path);
        if(root->right != NULL)
            find(root->right, expectNumber, currentSum, path);
        path.pop_back();
    }
private:
    vector<vector<int>> result;
};

面试题39:二叉树的深度

题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
解题思路:树常用的递归,父亲结点的深度是左右子树深度的较大值,停止条件是叶子结点的深度为1。

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot == NULL)
            return 0;
        return getDepth(pRoot);
    }
    int getDepth(TreeNode* pRoot){
        int leftDepth = 0, rightDepth = 0;
        if(pRoot->left != NULL)
            leftDepth = getDepth(pRoot->left);
        if(pRoot->right != NULL)
            rightDepth = getDepth(pRoot->right);
        return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
    }
};

举一反三:判断一棵树是不是平衡二叉树。平衡二叉树的每个结点的左右子树的深度差不超过1。最直观的做法是先分别得到左右子树的深度,判断该结点符不符合平衡条件,若不符合返回false,若符合再判断它的左子树和右子树,这样做的坏处在于重复计算了结点深度。所以如果用后序遍历的方式遍历结点,在遍历每个结点之前我们就已经遍历过它的左右子树,只要在遍历每个结点的时候记录它的深度,就可以一边遍历一边判断这个结点是不是平衡的。

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == NULL)
            return true;
        int pDepth;
        return IsBalanced(pRoot,pDepth);
    }
    bool IsBalanced(TreeNode* pRoot, int &pDepth){  //pDepth不要忘记加&
        if(pRoot == NULL){
            pDepth = 0;
            return true;
        }
        int left,right;
        if(IsBalanced(pRoot->left,left) && IsBalanced(pRoot->right,right)){  //后序遍历的体现
            if((left-right)>=-1 && (left-right)<=1){
                pDepth = 1+(left>right?left:right);
                return true;
            }
        }
        return false;
    }
};

面试题58:二叉树的下一个结点

题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路:如果这个结点有右子树,那么下一个结点就是右子树的最左子结点;如果这个结点没有右子树,且这个结点是父结点的左子结点,那么父结点就是下一个结点,如果这个结点是父结点的右结点,就需要沿着父指针一直向上遍历,直到找到一个是它父结点的左子节点的节点。

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == NULL)
            return NULL;
        TreeLinkNode* pNext = NULL;
        if(pNode->right != NULL){
            TreeLinkNode* pRight = pNode->right;
            while(pRight->left != NULL)
                pRight = pRight->left;
            pNext = pRight;
        }else if(pNode->next != NULL){ //此处next是指向父结点的指针
            TreeLinkNode* pCurrent = pNode;
            TreeLinkNode* pParent = pNode->next;
            while(pParent!=NULL && pCurrent == pParent->right){
                pCurrent = pParent;
                pParent = pCurrent->next;
            }
            pNext = pParent;
        }
        return pNext;
    }
};

面试题59:对称的二叉树

题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
解题思路:包括前序遍历在内的三种遍历方法,都是先遍历左子结点再遍历右子结点,所以定义一种与前序遍历对称的遍历算法,即先访问父结点再访问右子结点最后访问左子结点,注意到这两个遍历方式的到的序列是一样的。

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        return isSymmetrical(pRoot,pRoot);
    }
    bool isSymmetrical(TreeNode* pRoot1,TreeNode* pRoot2){
        if(pRoot1 == NULL && pRoot2 == NULL)
            return true;
        if(pRoot1 == NULL || pRoot2 == NULL)
            return false;
        if(pRoot1->val != pRoot2->val)
            return false;
        return isSymmetrical(pRoot1->left,pRoot2->right) && isSymmetrical(pRoot1->right,pRoot2->left);
    }
};

面试题60:把二叉树打印成多行

题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
解题思路:用队列来保存要打印的结点,但是为了把每一行单独打印一行,需要两个变量:一个变量curLayerCnt表示当前层中还没有打印的结点数,一个变量nextLayerCnt表示下一层结点的数目。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> result;
        if(pRoot == NULL)
            return result;
        queue<TreeNode*> tree;
        tree.push(pRoot);
        int curLayerCnt = 1;
        int nextLayerCnt = 0;
        vector<int> helpVec;//辅助vector,保存每一层的结点值
        while(!tree.empty()){
            TreeNode* tmp = tree.front();
            helpVec.push_back(tmp->val);
            tree.pop();
            if(tmp->left != NULL){
                tree.push(tmp->left);
                nextLayerCnt++;
            }
            if(tmp->right != NULL){
                tree.push(tmp->right);
                nextLayerCnt++;
            }
            curLayerCnt--;
            if(curLayerCnt == 0){
                result.push_back(helpVec);
                helpVec.clear();
                curLayerCnt = nextLayerCnt;
                nextLayerCnt = 0;
            }
        }
        return result;
    }
};

面试题61:按之字形顺序打印二叉树

题目:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
解题思路:写个例子就知道一层到下一层的打印顺序都是先进后出,所以要用栈来保存。仔细分析,可以发现奇数层和偶数层的规律,如果当前打印的是奇数层,需要先保存左子结点再保存右子结点到第一个栈里;如果当前打印的是偶数层,需要先保存右子结点再保存左子结点到第二个栈里。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> result;
        if(pRoot == NULL)
            return result;
        stack<TreeNode*> levels[2]; //定义了两个栈
        int current = 0;
        int next = 1;
        levels[current].push(pRoot);
        vector<int> helpVec; //辅助vector
        while(!levels[current].empty()){
            TreeNode* tmp = levels[current].top();
            levels[current].pop();
            helpVec.push_back(tmp->val);
            if(current == 0){  //当前层是奇数层
                if(tmp->left!=NULL)
                    levels[next].push(tmp->left);
                if(tmp->right!=NULL)
                    levels[next].push(tmp->right);
            }
            else{  //当前层是偶数层
                if(tmp->right!=NULL)
                    levels[next].push(tmp->right);
                if(tmp->left!=NULL)
                    levels[next].push(tmp->left);
            }
            if(levels[current].empty()){
                result.push_back(helpVec);
                helpVec.clear();
                current = 1 - current;
                next = 1 - next;
            }
        }

        return result;
    }
};

面试题62:系列化二叉树

题目:请实现两个函数,分别用来序列化和反序列化二叉树
解题思路:用前序遍历来序列化二叉树,因为前序遍历是从根节点开始的。当遍历碰到NULL指针时,这些NULL指针序列化成一个特殊的字符比如‘#’,数值之间要用一个特殊的字符比如‘,’来隔开。

class Solution {
public:
    char* Serialize(TreeNode *root) {    
        if(!root)
            return "#";
        string r = to_string(root->val);
        r.push_back(',');
        char* left = Serialize(root->left);
        char* right = Serialize(root->right);
        char* ret = new char[strlen(left) + strlen(right) + r.length()];
        strcpy(ret,r.c_str());
        strcat(ret,left);
        strcat(ret,right);
        return ret;
    }
    TreeNode* Decode(char *&str) {
        if(*str == '#'){
            str++;
            return NULL;
        }
        int num = 0;
        while(*str!=','){
            num = num*10+(*(str++))-'0';
        }
        str++;
        TreeNode* root = new TreeNode(num);
        root->left = Decode(str);
        root->right = Decode(str);
        return root;
    }
    TreeNode* Deserialize(char *str) {
        return Decode(str);
    }
};

面试题63:二叉搜索树的第k个结点

题目:给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。
解题思路:因为是二叉搜索树,所以用中序遍历即可。

class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot == NULL || k == 0)
            return NULL;
        return KthNodeCore(pRoot,k);
    }

    TreeNode* KthNodeCore(TreeNode* pRoot,int &k){
        TreeNode* target = NULL;
        if(pRoot->left != NULL)
            target = KthNodeCore(pRoot->left,k);
        if(target == NULL){
            if(k == 1)
                target = pRoot;
            else
                k--;
        }
        if(target == NULL && pRoot->right != NULL)
            target = KthNodeCore(pRoot->right,k);
        return target;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值