剑指offer---树刷题专题(c/c++),一篇文章就可以读懂树了吗?

1、重建二叉树

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

  • 先回顾一下,什么是前序遍历,中序遍历和后序遍历:
    (1)前序遍历:
    先访问根节点
    再先序访问左子树
    再先序右子树。
    (2)中序遍历:
    中序遍历左子树
    再访问根节点
    再中序遍历右子树
    (3)后序遍历:
    先后序遍历左子树
    再后序遍历右子树
    再访问根节点

  • 解题思路
    因为是树的结构,一般都是用递归来实现。
    (1)用数学归纳法的思想就是,假设最后一步,就是root的左右子树都已经重建好了,那么我只要考虑将root的左右子树安上去即可。
    (2)根据前序遍历的性质,第一个元素必然就是root,那么下面的工作就是如何确定root的左右子树的范围。
    (3)根据中序遍历的性质,root元素前面都是root的左子树,后面都是root的右子树。那么我们只要找到中序遍历中root的位置,就可以确定好左右子树的范围。只需要将确定的左右子树安到root上即可。递归要注意出口,假设最后只有一个元素了,那么就要返回。

  • 解题代码:

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        int vinLen = vin.size();
        if(vinLen == 0)
            return NULL;
        vector<int> leftPre,rightPre,leftVin,rightVin;
         //创建根节点,根节点肯定是前序遍历的第一个数
        TreeNode * tHead = new TreeNode(pre[0]);
        //找到中序遍历中,根节点的位置
        //对于中序遍历,根节点左边的节点位于二叉树的左边,根节点右边的节点位于二叉树的右边
       
        int gen;
        for(int i = 0; i < vinLen; i++)
        {
            if(vin[i] == pre[0])
            {
                gen = i;
                break;
            }
        }
        
        //对二叉树节点进行归并
        for(int i = 0; i < gen; i++)
        {
            leftPre.push_back(pre[i+1]);  //前序遍历第一个为根节点
            leftVin.push_back(vin[i]);
        }
        for(int i = gen+1; i < vinLen; i++)
        {
            rightPre.push_back(pre[i]);
            rightVin.push_back(vin[i]);
        }    
        
        //取出前序和中序遍历根节点左边和右边的子树
        //递归,再对其进行上述所有步骤,即再区分子树的左、右子子数,直到叶节点
        tHead->left = reConstructBinaryTree(leftPre,leftVin);
        tHead->right = reConstructBinaryTree(rightPre,rightVin);
        
        return tHead;
    }
};

2、树的子结构

  • 题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

  • 解题思路:

	1、首先设置标志位result = false,因为一旦匹配成功result就设为true,剩下的代码不会执行,如果匹配不成功,默认返回false
	2、递归思想,如果根节点相同则递归调用DoesTree1HaveTree2(),如果根节点不相同,则判断tree1的左子树和tree2是否相同,再判断右子树和tree2是否相同
	3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,DoesTree1HasTree2中,如果Tree2为空,则说明第二棵树遍历完了,即匹配成功,tree1为空有两种情况:
	(1)如果tree1为空&&tree2不为空说明不匹配,
	(2)如果tree1为空,tree2为空,说明匹配。
 
  • 解题代码:
/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result = false;
        if(pRoot1 != NULL && pRoot2 != NULL)
        {
             if(pRoot1->val == pRoot2->val)
            {
                result = DoesTree1HaveTree2(pRoot1,pRoot2);
            }
            if(!result)
            {
                result = HasSubtree(pRoot1->left,pRoot2);
            }
            if(!result)
            {
                result = HasSubtree(pRoot1->right,pRoot2);
            }
        }
       return result;
    }
    
    bool DoesTree1HaveTree2(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1 == NULL && pRoot2 != NULL)
            return false;
        if(pRoot2 == NULL)
            return true;
        if(pRoot1->val != pRoot2->val)
            return false;
        return DoesTree1HaveTree2(pRoot1->left,pRoot2->left)
            && DoesTree1HaveTree2(pRoot1->right,pRoot2->right);
    }
};

3、二叉树的镜像

  • 题目:操作给定的二叉树,将其变换为源二叉树的镜像。

  • 输入描述:
    在这里插入图片描述

  • 解题思路:
    二叉树左右交换,然后递归深度最深的一层;

  • 解题代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot == NULL)
            return ;
        TreeNode *pTemp;
        pTemp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = pTemp;
        
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

4、从上往下打印二叉树

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

  • 解题思路:
    可以借助队列来实现,先进先出,从上到下,从左到右;(代码比较好理解)

  • 解题代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        queue<TreeNode*> q;
        vector<int> res;
        if(root == NULL)
            return res;
        q.push(root);
        while(!q.empty())
        {
            root = q.front();
            q.pop();
            res.push_back(root->val);
            if(root->left)
            {
                q.push(root->left);
            }
            if(root->right)
            {
                q.push(root->right);
            }
        }
        return res;
    }
};

5、二叉搜索树的后序遍历序列

  • 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

  • 解题思路:

非递归也是一个基于递归的思想:
左子树一定比右子树小,因此去掉根后,数字分为left,right两部分,
right部分的最后一个数字是右子树的根他也比左子树所有值大,
因此我们可以每次只看有子树是否符合条件即可,
即使到达了左子树左子树也可以看出由左右子树组成的树还想右子树那样处理
对于左子树回到了原问题,对于右子树,
左子树的所有值都比右子树的根小可以暂时把他看出右子树的左子树
只需看看右子树的右子树是否符合要求即可
  • 解题代码:
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        int size = sequence.size();
        if(0 == size)
            return false;
 
        int i = 0;
        while(--size)
        {
            while(sequence[i++] < sequence[size]);
            while(sequence[i++] > sequence[size]);
 
            if(i < size)
                return false;
            i = 0;
        }
        return true;
    }
};

6、二叉树的深度

  • 题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

  • 解题思路递归,求深度最大的左右边

  • 解题代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(!pRoot)
            return 0;
        return max(1+TreeDepth(pRoot->left), 1+TreeDepth(pRoot->right));
    }
};

7、二叉树中和为某一值的路径

  • 题目:输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

  • 解题代码

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int> > buffer;
    vector<int> temp;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root == NULL)
            return buffer;
        //先把值填入
        temp.push_back(root->val);
        //找到一条路径,从根到叶节点,(根到叶节点的一步分不能算)
        if(expectNumber-root->val==0 && root->left == NULL && root->right == NULL)
        {
            buffer.push_back(temp);
        }
         //遍历左子树
        FindPath(root->left,expectNumber-root->val);
         //遍历右子树
        FindPath(root->right,expectNumber-root->val);
        //到叶节点后往后退一步(举个例子,假设到叶节点,(该节点为左子树),正好符合条件,填入,现在要判断该节点的父亲节点的右子树是否符合条件,是不是要后退一步)
        if(temp.size() != 0)
            temp.pop_back();
        return buffer;
    }
};

8、二叉搜索树与双向链表

  • 题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

  • 解题思路:中序遍历即可

  • 解题代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree == NULL)
            return NULL;
        TreeNode* pre = NULL;
        convertHelper(pRootOfTree,pre);
        
        TreeNode* res = pRootOfTree;
        while(res->left)
            res = res->left;
        return res;
    }
    
     void convertHelper(TreeNode* cur, TreeNode*& pre)
     {
        if(cur == NULL) 
            return;
         
        convertHelper(cur ->left, pre);
         
        cur ->left = pre;
        if(pre) pre ->right = cur;
        pre = cur;
         
        convertHelper(cur ->right, pre);
         
      }
};

9、平衡二叉树

  • 题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
    在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

  • 解题思路
    平衡二叉树:是指数中任一结点的左右子树深度相差不超过1
    所以可以先求各层深度,然后用递归实现判断

  • 解题代码:

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == NULL)
            return true;
        int left = TreeDepth(pRoot->left);
        int right = TreeDepth(pRoot->right);
        if(left > right+1 || left+1 < right)
            return false;
        else
            return IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right);
    }
    int TreeDepth(TreeNode* pRoot)
    {
        if(!pRoot)
            return 0;
        return max(1+TreeDepth(pRoot->left), 1+TreeDepth(pRoot->right));
    }
};

10、二叉树的下一个节点

  • 题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

  • 解题思路
    首先,中序遍历为:左根右;
    如果节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;
    如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。

  • 解题代码

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == NULL)
            return NULL;
        TreeLinkNode* next;
        //先右子节点的左子节点遍历
        if(pNode->right != NULL)
        {
            TreeLinkNode* rightNode = pNode->right;
            while(rightNode->left != NULL)
            {
                rightNode = rightNode->left;
            }
            next = rightNode;
        }
        //向父节点遍历
        else
        {
            TreeLinkNode* parentNode = pNode->next;
            TreeLinkNode* currentNode = pNode;
            while(parentNode != NULL  && currentNode == parentNode->right){
                currentNode = parentNode;
                parentNode = parentNode->next;
            }
            next = parentNode;
        }
        return next;
    }
};

11、对称二叉树

  • 题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

  • 解题思路:递归
    在这里插入图片描述
    根据上图可知,若为对称二叉树,需要满足如下条件:

1. L->val == R->val
2. L->left->val == R->right->val
3. L->right->val == R->left->val
  • 解题代码:
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        return isSymmetrical(pRoot,pRoot);
    }
    bool isSymmetrical(TreeNode* pRoot1,TreeNode* pRoot2)
    {
        if(pRoot1 == nullptr && pRoot2 == nullptr)
            return true;
        if(pRoot1 == nullptr || pRoot2 == nullptr)
            return false;
        if(pRoot1->val != pRoot2->val)
            return false;
        return isSymmetrical(pRoot1->left,pRoot2->right)&& isSymmetrical(pRoot1->right,pRoot2->left);
    }
 
};

12、按之字形顺序打印二叉树(标记一下)

  • **题目:**请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

  • 解题思路:
    可以用官方给的队列,也可以用栈

  • 解题代码:
    (1)队列实现

链接:https://www.nowcoder.com/questionTerminal/91b69814117f4e8097390d107d2efbe0?answerType=1&f=discussion
来源:牛客网

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> ret;
        if (!pRoot) return ret;
        queue<TreeNode*> q;
        q.push(pRoot);
        int level = 0;

        while (!q.empty()) {
            int sz = q.size();
            vector<int> ans;
            while (sz--) {
                TreeNode *node = q.front();
                q.pop();
                ans.push_back(node->val);

                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
            ++level;
            if (!(level&1)) // 偶数层 反转一下
                reverse(ans.begin(), ans.end());
            ret.push_back(ans);
        }
        return ret;
    }

};

(2)用两个栈实现

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        if(pRoot == nullptr)
            return res;
        stack<TreeNode*> stackL;
        stack<TreeNode*> stackR;
        TreeNode* popNode;
        vector<int> tmp;
        tmp.push_back(pRoot->val);
        res.push_back(tmp);
        tmp.clear();
        stackL.push(pRoot);
        
        while(!stackL.empty() || !stackR.empty())
        {
            while(!stackL.empty())
            {
                popNode = stackL.top();
                stackL.pop();
                if(popNode->right)
                {
                    stackR.push(popNode->right);
                    tmp.push_back(popNode->right->val);
                }
                if(popNode->left)
                {
                    stackR.push(popNode->left);
                    tmp.push_back(popNode->left->val);
                }
            }
            if(!tmp.empty())
            {
                res.push_back(tmp);
                tmp.clear();
            }
            while(!stackR.empty())
            {
                popNode = stackR.top();
                stackR.pop();
                if(popNode->left)
                {
                    stackL.push(popNode->left);
                    tmp.push_back(popNode->left->val);
                }
                if(popNode->right)
                {
                    stackL.push(popNode->right);
                    tmp.push_back(popNode->right->val);
                }
            }
            if(!tmp.empty())
            {
                res.push_back(tmp);
                tmp.clear();
            }
        }
        return res;
    }
};

13、把二叉树打印成多行

这道题和上一道一样,不过打印都是从左到右的,不存在奇数层和偶数层。套路是一样的。

  • 题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

  • 解决代码及思路:
    还是用队列实现,简单一些。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            vector<vector<int> > res;
            if(pRoot == nullptr)
                return res;
            queue<TreeNode*> q;
            q.push(pRoot);
            
            while(!q.empty())
            {
                int sz = q.size();
                vector<int> tmp;
                while(sz--)
                {
                    TreeNode* node = q.front();
                    q.pop();
                    tmp.push_back(node->val);
                    
                    if(node->left)
                        q.push(node->left);
                    if(node->right)
                        q.push(node->right);
                }
                res.push_back(tmp);
            }
            return res;
        }
};

14、序列化二叉树(标记一下)

  • 题目:请实现两个函数,分别用来序列化和反序列化二叉树
    二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
    二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
    例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

  • 解题思路,使用层次遍历实现
    层次遍历采用队列实现。跟先序遍历的思想差不多,无非都是把树的所有数据遍历一遍,然后记录下来。
    层次遍历模板为:

 void bfs(TreeNode *root)
    {
        queue<TreeNode*> qt;
        qt.push(root);
        string s;
        while(!qt.empty())
        {
            //Pop操作
            TreeNode *node = qt.front();
            qt.pop();
            
            //process node
            if(node == NULL)
            {
                s.push_back('#');
                s.push_back(',');
                continue;
            }
            s += to_string(node->val);
            s.push_back(',');
            
           //push 操作
            qt.push(node->left);
            qt.push(node->right);
        }
    }
  • 解决代码:
/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
   
    char* Serialize(TreeNode *root) {    
        queue<TreeNode*> qt;
        qt.push(root);
        string s;
        while(!qt.empty())
        {
            //Pop操作
            TreeNode *node = qt.front();
            qt.pop();
            
            //process null node
            if(node == nullptr)
            {
                s.push_back('#');
                s.push_back(',');
                continue;
            }
            //process not null node
            s += to_string(node->val);
            s.push_back(',');
            
           //push 操作
            qt.push(node->left);
            qt.push(node->right);
        }
        char *ret = new char[s.length() + 1];
        strcpy(ret,s.c_str());
        
        return ret;
    }
    TreeNode* Deserialize(char *str) {
        if (str == nullptr)
        {
            return nullptr;
        }
        // 可用string成员函数
        string s(str);
        if (str[0] == '#') 
                {
            return nullptr;
        }

        // 构造头结点
        queue<TreeNode*> nodes;
        TreeNode *ret = new TreeNode(atoi(s.c_str()));
        s = s.substr(s.find_first_of(',') + 1);
        nodes.push(ret);
        // 根据序列化字符串再层次遍历一遍,来构造树
        while (!nodes.empty() && !s.empty())
        {
            TreeNode *node = nodes.front();
            nodes.pop();
            if (s[0] == '#')
            {
                node->left = nullptr;
                s = s.substr(2);
            }
            else
            {
                node->left = new TreeNode(atoi(s.c_str()));
                nodes.push(node->left);
                s = s.substr(s.find_first_of(',') + 1);
            }

            if (s[0] == '#')
            {
                node->right = nullptr;
                s = s.substr(2);
            }
            else
            {
                node->right = new TreeNode(atoi(s.c_str()));
                nodes.push(node->right);
                s = s.substr(s.find_first_of(',') + 1);
            }
        }
        return ret;
    }
};

15、二叉搜索树的第k个节点

  • 题目:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

  • 解题思路中序遍历的结果就是有序序列。所以,按照中序遍历顺序找到第k个结点就是结果。

  • 解题代码:

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    void inOrder(TreeNode* pRoot,TreeNode* &ans)
    {
        if(pRoot)
        {
            inOrder(pRoot->left,ans);
            count--;
            if(!count)
                ans = pRoot;
            inOrder(pRoot->right,ans);
        }
    }
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(!pRoot || k < 1)
            return nullptr;
        TreeNode* ans = NULL;
        count = k;
        inOrder(pRoot,ans);
        return ans;
    }

private:
    int count;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值