二叉树的一些经典题目

目录

1. 二叉树创建字符串。OJ链接

2. 二叉树的分层遍历1。OJ链接

3. 二叉树的分层遍历2。OJ链接

4.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。OJ链接

5. 二叉树搜索树转换成排序双向链表。OJ链接

6. 根据一棵树的前序遍历与中序遍历构造二叉树。 OJ链接

7. 根据一棵树的中序遍历与后序遍历构造二叉树。OJ链接

8. 二叉树的前序遍历,非递归迭代实现 。OJ链接

9. 二叉树中序遍历 ,非递归迭代实现。OJ链接

10. 二叉树的后序遍历 ,非递归迭代实现。OJ链接


1. 二叉树创建字符串。OJ链接

你需要采用前序遍历的方式,将一个二叉树转换成一个有括号和整数组成的字符串.

空结点则用一对括号"()"表示.而且你需要省略所有不影响字符串于原始二叉树之间的一对一映射关系的括号对.

示例:

  

输出:"1(2()(4))(3)" 

思路:

题目要求我们省略所有不影响字符串于原始二叉树之间的一对一映射关系的括号对,也就是保留必要的括号对.

题目要求采用前序遍历的方式,因此对于一棵不为空的二叉树,我们首先因该将该二叉树的根结点的值放入字符串当中,而对于该树的左孩子,只有概述属于下面两种情况之一时,才有必要将该树的左孩子的之放入到字符串当中.

因为对于情况二来说,如果不将其左孩子的值(空括号对)放入字符串中,我们将不能区分结点三是结点一的左孩子还是右孩子,二对于二叉树的右孩子,只要右孩子不为空我们就需要将右孩子的值放入字符串.

因此,字符串的创建过程如下:

  1. 若二叉树不为空,则先根据结点的值放入字符串.
  2. 若左子树不为空,或者左子树为空但右子树不为空,则需要将左子树的值放入字符串中.
  3. 若右子树部位空,则需要将右子树的值放入字符串中. 

其中,操作根的结点的左右子树的规则也是一样.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    string tree2str(TreeNode* root) {
        string str;
        if(root == nullptr)
            return str;
        str += to_string(root->val);
        if(root->left||root->right)
        {
            str+='(';
            str+=tree2str(root->left);
            str+=')';
        }
        if(root->right)
        {
            str+='(';
            str+=tree2str(root->right);
            str+=')';
        }
        return str;
    }
};

对代码分析图:

该代码虽然能够成功创建字符串,但是代码有一个致命的问题:当所给出的二叉树的结构比较大时,因为该函数是传值返回,所以在创建字符串时会存在大量的string深拷贝,为了解决该问题,我们可以选择封装一个子函数,子函数的传参使用引用传参,这时便能避免进行string的深拷贝问题:

class Solution {
public:
    string tree2str(TreeNode* root) {
        string str;
        _tree2str(root,str);
        return str;
    }
    void _tree2str(TreeNode* root,string& str)
    {
        if(root == nullptr)
            return;
        str+=to_string(root->val);
        if(root->left||root->right)
        {
            str+='(';
            _tree2str(root->left,str);
            str+=')';
        }
        if(root->right)
        {
            str+='(';
            _tree2str(root->right,str);
            str+=')';
        }
    }
};

 此代码的分析和上面类似,在这就不再分析了.

2. 二叉树的分层遍历1OJ链接

给你一个二叉树,请你返回其按层序遍历得到的结点值.(即逐层的,从左往右访问所有结点).

示例:

思路:

改题目考察的就是二叉树的层序遍历,只不过要求我们将遍历的结构发入vector容器当中返回而已.

解决该题目需要一个队列q和两个vector容器,队列q用于存储结点的地址,两个vector容器当中,v用于存储二叉树某一层结点的值,vv用于存储整个二叉树的结点值.

操作步骤如下:

  1. 先将根结点入队列.
  2. 从队头取levelsize个(初始levelsize为1)放入容器v,若取出结点的左孩子不为空,还需要将不为空的孩子结点放入队列.
  3. 将容器v放入容器vv当中,即第该层的结点值.
  4. 更新levelsize的值为当前队列当中的元素个数,继续步骤二和步骤三的操作,直至队列为空.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root == nullptr)
        {
            return vector<vector<int>>();
        }
        queue<TreeNode*> q;
        q.push(root);

        int levelSize = 1;
        vector<vector<int>> vv;
        while(!q.empty())
        {
            vector<int> v;
            for(int i = 0;i<levelSize;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);
                if(front->left)
                {
                    q.push(front->left);
                }
                if(front->right)
                {
                    q.push(front->right);
                }
            }
            vv.push_back(v);
            levelSize = q.size();
        }
        return vv;
    }
};

代码分析:

3. 二叉树的分层遍历2OJ链接

其实二叉树的层序遍历二是二叉树的层序遍历一演变过来的,只需要在最后返回的时候把vector容器逆序过来即可。

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<int> v;
        vector<vector<int>> vv;
        if(root == nullptr)
            return vv;
        queue<TreeNode*> q;
        q.push(root);
        int levelSize = 1;
        while(!q.empty())
        {
            for(int i = 0;i<levelSize;i++)
            {
                TreeNode* top = q.front();
                q.pop();
                v.push_back(top->val);
                if(top->left)
                {
                    q.push(top->left);
                }
                if(top->right)
                {
                    q.push(top->right);
                }
            }
            vv.push_back(v);
            v.clear();
            levelSize = q.size();
        }
        reverse(vv.begin(),vv.end());
        return vv;
    }
};

代码分析:

前面的在上一题分析过了。

4.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。OJ链接

题目:

给定一个二叉树,找到该树中两个指定结点的最近公共祖先,最近公共祖先的定义为"对于有根树T的两个结点p,q,最近公共祖先表示为一个结点x,满足x是p,q的祖先且x的深度尽可能大(一个结点也可以是自己的祖先).

示例:

思路:以上面的列题为列,以root为结点从上往下看,5和4都在root的左子树上所以root不是5,和4的公共祖先,而且5和4又都在root的左子树上,所以把root的左孩子给root,root == p||root == q,证明p或q自己既是祖先又是孩子.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isTree(TreeNode* root,TreeNode* x)
    {
        if(root == nullptr)
            return false;
        if(root == x)
            return true;
        return isTree(root->left,x)||isTree(root->right,x);

    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p||root == q)
        {
            return root;
        }

        bool pInLeft = isTree(root->left,p);
        bool pInRight = !pInLeft;

        bool qInLeft = isTree(root->left,q);
        bool qInRight = !qInLeft;

        if((pInLeft && qInRight)||(pInRight && qInLeft))
        {
            return root;
        }

        if(pInLeft && qInLeft)
        {
            return lowestCommonAncestor(root->left,p,q);
        }
        if(pInRight && qInRight)
        {
            return lowestCommonAncestor(root->right,p,q);
        }
        return nullptr;
    }
};

但是解法的时间复杂度过高为O(N^2),下面提供一种时间复杂度为O(logN)的解法:

class Solution {
public:
    bool FindPath(TreeNode* root,TreeNode* x ,stack<TreeNode*>& path)
    {
        if(root == nullptr)
            return false;
        path.push(root);

        if(root == x)
            return true;
        if(FindPath(root->left, x, path))
            return true;
        if(FindPath(root->right,x, path))
            return true;
        path.pop();
            return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pPath,qPath;
        FindPath(root,p,pPath);
        FindPath(root,q,qPath);

        while(pPath.size()!= qPath.size())
        {
            if(pPath.size()>qPath.size())
            {
                pPath.pop();
            }
            else
            {
                qPath.pop();
            }
        }
        while(pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }
        return qPath.top();
    }
};

5. 二叉树搜索树转换成排序双向链表。OJ链接

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表,入下图:

注意:

  1. 要求不能创建任何新的结点,只能调整树中结点的指针的指向,当转化完成以后,树中结点的左指针需要指向前驱,树中结点的右指针需要指向后继.
  2. 返回链表当中第一个结点的指针.
  3. 函数返回TreeNode,有左右指针,其实可以看成一个双向链表的数据结构.
  4. 你不用输出双向链表,程序会根据你的返回自动打印输出.

示例:

输入:{10,6,14,4,8,12,16}

返回值:From left ro right :4,6,8,10,12,14,16

          From rihgt to left :16,14,12,10,8,6,4

输入上图二叉树,返回双向链表的头结点即可.

思路:

因为题目所给二叉树是搜索二叉树,而搜索二叉树的中序遍历就是升序,所以我们可以按照中序的逻辑进行处理.既然是双向循环链表,那么只有一个变量遍历二叉树是显然不够的,在转换过程中至少需要两个遍历进行处理:

  1. cur:标记当前遍历到的结点.
  2. prev:标记上一次遍历到的结点,即转换成双向链表的前驱结点.

此时我们便可以从根结点开始对二叉树进行处理了,若遍历到的cur结点为空,自然无需处理,当遍历到的cur结点不为空时,处理逻辑如下:

  1. 先处理cur的左子树.
  2. 在处理cur结点.处理时,先让cur左指针(前驱指针)指向上次遍历到的结点prev,在让prev的右指针(后继指针)指向结点cur,此时便建立了结点cur和prev之间的双向关系,此后早更新prev的值即可.
  3. 最后处理cur的右子树.

而处理cur左右子树的逻辑于处理最初的二叉树的逻辑无疑,处理完后,我们只需要从根结点开始,一直找其前驱结点,最终便可以找到双向链表中的第一个结点进行返回.

处理后的二叉搜索树的各个结点的左右指针如下:

将其拉直后实际上就是一个双向连表:

代码如下:


struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};
class Solution {
public:
	void InorderConvert(TreeNode* cur,TreeNode*& prev)
	{
		if(cur == nullptr)
			return ;
		InorderConvert(cur->left, prev);
		cur->left = prev;
		if(prev)
			prev->right = cur;
		prev = cur;
		InorderConvert(cur->right, prev);
	}
    TreeNode* Convert(TreeNode* pRootOfTree) {
        TreeNode* cur = pRootOfTree,*prev = nullptr;
		InorderConvert(cur, prev);
		TreeNode* head = pRootOfTree;
		while(head&&head->left)
		{
			head = head->left;
		}
		return head;
    }
};

注意:

  1. 我们在处理cur结点时,都要让cur结点的左指针指向pre,为了处理个结点时逻辑统一,因此prev的初值最好为nullptr,这样第一个被我们处理的结点(最后一个左路结点)的左指针被处理时便也能理所当然的指向空了.
  2. pre在传参时采用引用传参,因为我们不希望在递归函数返回时自主改变pre的指向,相反,我们希望cur随着递归函数的返回自主改变cue的指向,从而才能正确遍历二叉树,因此cur在传参时不采用引用传参.
  3. 若想使得转换后的双向链表为循环双向链表,只需要在转换后建立链表收尾结点之间的双向关系即可,从根结点出发一直往右走即可找到链表中的最后一个结点.

6. 根据一棵树的前序遍历与中序遍历构造二叉树。 OJ链接

给定一棵树的前序遍历preorder于中序遍历inorder,请构造二叉树并返回其根结点.

示例:

     输入:preorder=[3,9,20,15,7],inorder=[9,3,15,20,7]

输出=[3,9,20,null.null,15,7]

思路:

构建二叉树采用前序构建的方式:

1.构建根结点

2.构建左子树

3.构建右子树

根据所给前序遍历,我们可以一次获取所建子树的根结点值,在构建某一子树时,我们可以找出该子树根结点在其中序遍历序列当中的位置,进而将该子树的中序遍历划分为左子树和右子树的中序遍历.从而进行后续其左右子树的构建.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder,int& pi,vector<int>&inorder,int inStar,int inEnd)
    {
        if(inStar>inEnd)
            return nullptr;
        //建立该子树得根结点
        TreeNode* root =new TreeNode(preorder[pi]);
        pi++;
        int rooti = inStar;
        while(inorder[rooti] != root->val)
        {
            rooti++;
        }
        //递归构建该子树的左子树和右子树
        root->left = _buildTree(preorder,pi,inorder,inStar,rooti-1);
        root->right = _buildTree(preorder,pi,inorder,rooti+1,inEnd);
        //返回所构建子树的根
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int i = 0;//表示当前构建子树的结点在preorder当中的下标
        return _buildTree(preorder,i,inorder,0,inorder.size()-1);
    }
};

7. 根据一棵树的中序遍历与后序遍历构造二叉树。OJ链接

给定一棵树的中序遍历inorder与后序遍历postorder,请构造二叉树并返回其根结点.

示例:

输入:inorder=[9,3,15,20,7],postorder=[9,15,7,20,3]

输出:[3,9,20,null,null,15,7]

思路:

构建二叉树的方式如下:

1.构建根结点.

2.构建右子树.

3.构建左子树

根据所给出后续遍历序列,我们可以一次获得所需构建子树的根结点值,在构建某一子树时,我们可以找出该子树根结点在其中序遍历序列当中的位置,进而将该子树的中序遍历划分为其左子树和右子树的中序遍历,从而进行后续其左右子树的构建.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* _buildTree(vector<int>& inorder,int instart,int inend,vector<int>& postorder,int& pi)
    {
        if(instart>inend)
            return nullptr;
        //构建该子树的根结点
        TreeNode* root = new TreeNode(postorder[pi]);
        pi--;
        //将该子树的中序划分其左子树和右子树的中序遍历
        int rooti = instart;
        while(inorder[rooti]!=root->val)
        {
            rooti++;
        }
        //递归创建该子树的右左子树
        root->right = _buildTree(inorder,rooti+1,inend,postorder,pi);
        root->left = _buildTree(inorder,instart,rooti-1,postorder,pi);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int i = postorder.size()-1;
        return _buildTree(inorder,0,inorder.size()-1,postorder,i);
    }
};

注意:构建完某一子树的根节点后,先递归构建该子树的右子树,在递归构建该子树的左子树,因为所给序列是二叉树的后序遍历,后序遍历顺序为:左子树->右子树->根,我们从后向前使用后序遍历序列的结点值,构建而叉树时的构建顺序就是:根->右子树->左子树.

8. 二叉树的前序遍历,非递归迭代实现 。OJ链接

二叉树的前序遍历非常简单,无非就是利用栈来帮助递归的实现,用于过于简单下面直接给上代码.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        while(cur||!s.empty())
        {
            while(cur)
            {
                v.push_back(cur->val);
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            s.pop();
            cur = top->right;
        }
        return v;
    }
};

9. 二叉树中序遍历 ,非递归迭代实现。OJ链接

二叉树的中序遍历与前序遍历非常类似.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur||!s.empty())
        {
            while(cur)
            {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            s.pop();
            v.push_back(top->val);
            cur = top->right;
        }
        return v;
    }
};

10. 二叉树的后序遍历 ,非递归迭代实现。OJ链接

后序遍历需要定义一个prev,如果top->right == nullptr或者访问过top->right,则啥也不做,否则把top->right入栈.

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* prev = nullptr;
        while(cur || !s.empty())
        {
            while(cur)
            {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            if(top->right==nullptr||top->right == prev)
            {
                s.pop();
                v.push_back(top->val);
                prev = top;
            }
            else
            {
                cur = top->right;
            }
        }
        return v;
    }
};

(1)非递归定义 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除结点外n0 , 其余的每一个结点都有且仅有一个直接前驱结点;有零个或多个直接后继结点。 (2)递归定义 一颗大树分成几个大的分枝,每个大分枝再分成几个小分枝,小分枝再分成更小的分枝,… ,每个分枝也都是一颗树,由此我们可以给出树的递归定义。 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除根结点之外的其他结点分为m(m≥0)个互不相交的集合T0,T1,…,Tm-1,其中每个集合Ti(0≤i<m)本身又是一棵树,称为根的子树(subtree)。 2、掌握树的各种术语: (1) 父母、孩子与兄弟结点 (2) 度 (3) 结点层次、树的高度 (4) 边、路径 (5) 无序树、有序树 (6) 森林 3、二叉树的定义 二叉树(binary tree)是由n(n≥0)个结点组成的有限集合,此集合或者为空,或者由一个根结点加上两棵分别称为左、右子树的,互不相交的二叉树组成。 二叉树可以为空集,因此根可以有空的左子树或者右子树,亦或者左、右子树皆为空。 4、掌握二叉树的五个性质 5、二叉树的二叉链表存储。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值