树专题笔记

树的输入

typedef struct Node{
int val,
struct Node* left,
struct Node*  right,
}BNode,*TreeNode; 

void build_tree(TreeNode * root){
	int c;
	cin>>c;
	if(c=='.'){
		root=NULL;
	}
	else{
		root=(TreeNode*)malloc(sizeof(BNode));
		root->val=c;
		build_tree(root->left);
		build_tree(root->right);
	}
}
一、树的非递归遍历
1、前序遍历(两种写法)

方法一:
第一个算法是DFS的一种二叉树树的特例,与层次遍历类似(层次遍历使用队列,这种相似性源于BFS和DFS的相似性),并且有助于我们实现后序遍历的一个版本。具体算法如下:

  • 首先将树根压栈。
  • 在栈不为空的情况下,将当前指针更新为栈顶元素,弹出栈顶元素并加入到结果数组中,在当前节点的右孩子不为空的情况下,将其压入栈中,在root的左孩子不为空的情况下,压入栈中。
  • 返回第2步
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
 //前序遍历
vector<int> preOrder(TreeNode* root)
{
    vector<int> res;
    if (root == nullptr) return res;
    stack<TreeNode *> s;
    s.push(root);
    while (!s.empty())
    {
        root = s.top();
        res.push_back(root->val);
        s.pop();
        if (root->right != nullptr)
            s.push(root->right);
        if (root->left != nullptr)
            s.push(root->left);
    }
    return res;
} 
..........................................
/*层次遍历*/
 vector<int> printFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if(root == nullptr) return res;
        queue<TreeNode *> q;
        q.push(root);

        while(!q.empty())
        {
            root = q.front();
            q.pop();
            res.push_back(root->val);
            if(root->left != nullptr)
                q.push(root->left);
            if(root->right != nullptr)
                q.push(root->right);
        }
        return res;
    }

方法二:

第二个算法与后面所述的中序遍历方法类似,思路是不管是前中后序哪一种遍历方式,实际上都是从树根开始,然后先向左子树深入,再向右子树深入的过程,不同的是访问节点的顺序,先序是第一次,中序是第二次,后序是第三次,因此按这种思路,它们的代码逻辑应该很相似,区别在于处理节点的时机。而后序之所以难,是因为它是第三次访问时才处理节点,而我们利用栈结构,入栈是第一次,出栈是第二次,没有第三次的时机。以下为具体算法(对于先序遍历,栈中的元素是都已经处理过的元素):

//前序遍历
  vector<int> preOrder(TreeNode* root) {
    vector<int> res;
    if(root == nullptr) return res;
    stack<TreeNode *> s;
    while(!s.empty() || root!=nullptr)
    {
        if(root != nullptr)
            {
                s.push(root);
                res.push_back(root->val);
                root = root->left;
            }
        else 
        {
            root = s.top()->right;
            s.pop();

        }
    }
    return res;
    }

2.1 中序遍历

对于中序遍历,栈顶元素弹出时是被第二次访问的时候
中序遍历的算法步骤为:

  • 当栈不为空且当前指针不为空时,进入循环
  • 如果当前指针不为空,将指针所指的节点压入栈中,当前指针向左移动
  • 如果当前指针为空,弹出栈顶的元素,处理这个栈顶元素,并将当前指针更新为这个弹出元素的右孩子
  • 返回第一步
/*中序遍历*/ 
vector<int> inOrder(TreeNode *root)
{
    vector<int> res;
    if(root == nullptr) return res;
    stack<TreeNode *> s;

    while(!s.empty() || root!=nullptr) 
    {
        if(root != nullptr)
            {
                s.push(root);
                root = root->left;
            }
        else 
        {
            root = s.top();
            res.push_back(root->val);
            s.pop();
            root = root->right;
        }
    }
    return res;
}

2.2 实现一个遍历二叉查找树的迭代器(用根结点初始化)。
调用next()方法将返回二叉查找树中的一个后继。

注意
next()方法和hasNext()方法的时间复杂度为均摊O(1),空间复杂度为O(h)

  • 用栈来模拟BST的中序遍历过程,当前结点进栈,代表它的左子树正在被访问。栈顶结点代表当前访问到的结点。
  • 求后继时,只需要弹出栈顶结点,取出它的值。然后将它的右儿子以及右儿子的左儿子等一系列结点进栈,这一步代表找右子树中的最左子结点,并记录路径上的所有结点。
  • 判断是否还存在后继只需要判断栈是否为空即可,因为栈顶结点是下一次即将被访问到的结点。
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class BSTIterator {
public:
    stack<TreeNode*> st;
    BSTIterator(TreeNode *root) {
        TreeNode *p = root;
        while (p) {
            st.push(p);
            p = p -> left;
        }
    }

    /** @return whether we have a next smallest number */
    bool hasNext() {
        return !st.empty();
    }

    /** @return the next smallest number */
    int next() {
        TreeNode *cur = st.top();
        st.pop();
        int v = cur -> val;
        cur = cur -> right;
        while (cur) {
            st.push(cur);
            cur = cur -> left;
        }
        return v;
    }
};

/**
 * Your BSTIterator will be called like this:
 * BSTIterator i = BSTIterator(root);
 * while (i.hasNext()) cout << i.next();
 */

3.后序遍历

方法一:(对应于前序遍历的方法一)

class Solution {
public:
    vector<int> postorderTraversal(TreeNode *root) {
        stack<TreeNode*> stk;
        vector<int>res;
        if(!root) return res;
        stk.push(root);
        while(stk.size()){
            auto s=stk.top();
            if(!s->left&&!s->right){
                res.push_back(s->val);
                stk.pop();
            }
            if(s->right){
                stk.push(s->right);
                s->right=NULL;//该节点遍历过了就将它置为空,防止回溯的时候又重来一遍
            }
            if(s->left){
                stk.push(s->left);
                s->left=NULL;
            }
        }
        return res;
    }
};

方法二:(对应于前序遍历的方法二)
先将先序遍历的根-左-右变成根-右-左,再翻转变为左-右-根,翻转方式为改变结构res的加入顺序,然后把更新辅助结点p的左右顺序换一下。

vector<int> PostorderTraversal(TreeNode* root){
    vector<int>res;
    stack<TreeNode*>s;
    TreeNode *p = root;
    while(!s.empty() || p){
        if(p){
            s.push(p);
            res.insert(res.begin(),p->val);
            p = p->right;
        }
        else{
            TreeNode *t = s.top()
            s.pop();
            p = t->left;
        }
        return res;
    }
}

4.1不分行从上往下打印二叉树
class Solution {
public:
    vector<int> printFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if(!root) return res;
        
        queue<TreeNode*> q;
        q.push(root);
        
        while(q.size()){
            auto t=q.front();
            q.pop();
            res.push_back(t->val);
            if(t->left)q.push(t->left);
            if(t->right)q.push(t->right);
        }
        return res;
    }
};

4.2分行从上往下打印二叉树

class Solution {
public:
    vector<vector<int>> printFromTopToBottom(TreeNode* root) {
        vector<vector<int>> res;
        if(!root) return res;
        
        vector<int> level;
        
        queue<TreeNode*> q;
        q.push(root);
        q.push(NULL);
        
        while(q.size()){
            auto t=q.front();
            q.pop();
            
            if(!t){
                if(level.empty()) break;//下一层为空,则已经遍历到了叶子节点的最后一个节点,则可以退出了
                res.push_back(level);
                level.clear();
                q.push(NULL);//该层的所有下一层的子节点都加入队列了,最后在下一层最后加一个空节点
                continue;
            }
            
            level.push_back(t->val);
            if(t->left)q.push(t->left);
            if(t->right)q.push(t->right);
        }
        return res;
    }
};

4.3之字形顺序从上向下打印二叉树。

class Solution {
public:
    vector<vector<int>> printFromTopToBottom(TreeNode* root) {
        vector<vector<int>> res;
        if(!root) return res;
        
        vector<int> level;
        
        queue<TreeNode*> q;
        q.push(root);
        q.push(NULL);
        
        bool zig=false;//false从左到右
        while(q.size()){
            auto t=q.front();
            q.pop();
            
            if(!t){
                if(level.empty()) break;
                if(zig) reverse(level.begin(),level.end());
                res.push_back(level);
                level.clear();
                q.push(NULL);
                zig=!zig;
                continue;
            }
            
            level.push_back(t->val);
            if(t->left)q.push(t->left);
            if(t->right)q.push(t->right);
        }
        return res;
    }
};

5.1 给定一个整数 n,求以 1…n 为节点组成的二叉搜索树有多少种?
在这里插入图片描述

/*
设f[i]表示共有i个节点时,能产生的BST树的个数
n == 0 时,空树的个数必然为1,因此f[0] = 1
n == 1 时,只有1这个根节点,数量也为1,因此dp[1] = 1
n == 2时,有两种构造,f[2] = f[0] * f[1] + f[1] * f[0]
n == 3时,构造方法如题目给的示例所示,f[3] = f[0] * f[2] + f[1] * f[1] + f[2] * f[0]
同时,当根节点元素为 1, 2, 3, 4, 5, ..., i, ..., n时,基于以下原则的BST树具有唯一性:
以i为根节点时,其左子树 构成为[0,...,i-1],其右子树构成为[i+1,...,n]构成*/

class Solution {
public:
    int numTrees(int n) {
        vector<int> f(n+1);
        f[0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=i-1;j++)
                f[i]+=f[j]*f[i-j-1];
        return f[n];
    }
};

5.2 给定一个整数 n,返回所有结构不同的 BST (二叉搜索树),满足中序遍历是 1…n
递归搜索所有方案。

  • 对于每段连续的序列 l,l+1,…r,枚举二叉搜索树根节点的位置;
  • 分别递归求出左右子树的所有方案;
  • 左子树的任意一种方案和右子树的任意一种方案拼在一起,可以得到当前节点的一种方案,所以我们将左右子树的所有方案两两组合,并记录在答案中。
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<TreeNode*> generateTrees(int n) {
        //if (!n) return dfs(1,0);
        return dfs(1, n);
    }

    vector<TreeNode*> dfs(int l, int r)
    {
        vector<TreeNode*>res;
        if (l > r)
        {
            res.push_back(NULL);
            return res;
        }
        for (int i = l; i <= r; i ++ )
        {
            vector<TreeNode*>left = dfs(l, i - 1)
                , right = dfs(i + 1, r);
            for (auto &lc : left)
                for (auto &rc : right)
                {
                    TreeNode *root = new TreeNode(i);
                    root->left = lc;
                    root->right = rc;
                    res.push_back(root);
                }
        }
        return res;
    }
};

6.1 给出一棵树的前序遍历和中序遍历,请构造这颗二叉树
在这里插入图片描述
递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。

具体步骤如下:

  • 先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
  • 在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;
  • 假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
  • 有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;

时间复杂度分析
我们在初始化时,用哈希表(unordered_map<int,int>)记录每个值在中序遍历中的位置,这样我们在递归到每个节点时,在中序遍历中查找根节点位置的操作,只需要 O(1) 的时间。此时,创建每个节点需要的时间是 O(1),所以总时间复杂度是 O(n)。

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    unordered_map<int,int> hash;
    vector<int> inorder,preorder;
    TreeNode *buildTree(vector<int> &_preorder, vector<int> &_inorder) {
        inorder=_inorder,preorder=_preorder;
        int n=preorder.size(),m=inorder.size();
        for(int i=0;i<m;i++){
            hash[inorder[i]]=i;
        }
        return dfs(0,n-1,0,m-1);
    }
    
    TreeNode* dfs(int pl,int pr,int il,int ir){
        if(pl>pr)return NULL;
        auto root=new TreeNode(preorder[pl]);
        int k=hash[root->val];
        root->left=dfs(pl+1,pl+k-il,il,k-1);
        root->right=dfs(pl+k-il+1,pr,k+1,ir);
        return root;
    }
};

6.2 给出一棵树的中序遍历和后序遍历,请构造这颗二叉树

class Solution {
public:
    unordered_map<int,int> hash;
    vector<int> inorder,postorder;
    TreeNode *buildTree(vector<int> &_inorder, vector<int> &_postorder) {
        inorder=_inorder,postorder=_postorder;
        int n=postorder.size(),m=inorder.size();
        for(int i=0;i<n;i++){
            hash[inorder[i]]=i;
        }
        return dfs(0,n-1,0,m-1);
    }
    
    TreeNode* dfs(int pl,int pr,int il,int ir){
        if(pl>pr)return NULL;
        auto root=new TreeNode(postorder[pr]);
        int k=hash[root->val];
        root->left=dfs(pl,pl+k-il-1,il,k-1);
        root->right=dfs(pl+k-il,pr-1,k+1,ir);
        return root;
    }
};

7.1 在每个节点中填充下一个右指针

在这里插入图片描述
(BFS,树的遍历) O(n)
从根节点开始宽度优先遍历,每次遍历一层,遍历时按从左到右的顺序,对于每个节点,先让左儿子指向右儿子,然后让右儿子指向下一个节点的左儿子。最后让这一层最右侧的节点指向NULL。
遍历到叶节点所在的层为止。

为了便于理解,我们模拟一下样例的操作流程:

  • 第一步,遍历根节点,我们让根节点的左儿子指向右儿子,即让2指向3;
  • 第二步,从左到右遍历第二层,先遍历2,让2的左儿子指向右儿子,即让4指向5,再让2的右儿子指向下一个节点的左儿子,即5指向6;然后遍历3,依次类推;
  • 第三步,我们发现第三层已经是叶节点,算法终止;

时间复杂度分析:
每个节点仅会遍历一次,遍历时修改指针的时间复杂度是 O(1),所以总时间复杂度是 O(n)。

/**
 * Definition for binary tree with next pointer.
 * struct TreeLinkNode {
 *  int val;
 *  TreeLinkNode *left, *right, *next;
 *  TreeLinkNode(int x) : val(x), left(NULL), right(NULL), next(NULL) {}
 * };
 */
class Solution {
public:
    void connect(TreeLinkNode *root) {
        if(!root) return ;
        TreeLinkNode* last=root;
        while(last->left){//直到有叶子节点
            for(auto p=last;p;p=p->next){
                p->left->next=p->right;
                if(p->next)p->right->next=p->next->left;
                else p->right->next=NULL;
            }
            last=last->left;//开始遍历下一层
        }
    }
};

7.2 在一个任意的二叉树中每个节点中填充下一个右指针

class Solution {
public:
    void connect(TreeLinkNode *root) {
        while(root){//x循环队列的下一个元素
            auto dummy=new TreeLinkNode(-1);
            auto cur=dummy;
            while(root){//循环这一行的元素
                if(root->left){
                    cur->next=root->left;
                    cur=cur->next;
                }
                if(root->right){
                    cur->next=root->right;
                    cur=cur->next;
                }
                root=root->next;
            }
            cur->next=NULL;
            root=dummy->next;
        }
    }
};

8.1 判断给定的二叉树是否是平衡的

class Solution {
public:
    bool res=true;
    bool isBalanced(TreeNode *root) {
        dfs(root);
        return res;
    }
    
    int dfs(TreeNode* root){
        if(!root) return 0;
        int left=dfs(root->left);
        int right=dfs(root->right);
        if(abs(left-right)>1) res=false;
        return max(left,right)+1;
    }
};

8.2 给定一棵二叉树,判断琪是否是自身的镜像(即:是否对称)

class Solution {
public:
    bool isSymmetric(TreeNode *root) {
        if(!root) return true;
        return dfs(root->left,root->right);
    }
    
    bool dfs(TreeNode* l, TreeNode* r)
    {
        if(!l||!r) return !l&&!r;
        return l->val==r->val&&dfs(l->left,r->right)&&dfs(l->right,r->left);
    }
};

8.3 判断给出的二叉树是否是一个二叉搜索树(BST)
二叉搜索树的定义如下:
一个节点的左子树上节点的值都小于自身的节点值
一个节点的右子树上节点的值都小于自身的节点值
所有节点的左右子树都必须是二叉搜索树

深度优先遍历整棵子树。
遍历时,需要向上传递当前子树中的最小值和最大值,这里可以用C++中的引用来专递。
对于当前节点,我们先遍历它的左子树,判断左子树是否合法,同时判断左子树的最大值是否小于当前节点的值;然后遍历右子树,判断右子树是否合法,同时判断右子树的最小值是否大于当前节点的值。
如果条件均满足,说明以当前节点为根的子树是一棵合法的二叉搜索树,返回true。

时间复杂度分析:树中每个节点仅被遍历一遍,所以时间复杂度是 O(n)O(n)。

class Solution {
public:
    bool isValidBST(TreeNode *root) {
        if(!root) return true;
        int maxv,minv;
        return dfs(root,maxv,minv); 
    }
    
    bool dfs(TreeNode *root,int &maxv,int &minv){
        maxv=minv=root->val;
        if(root->left){
            int curmax,curmin;
            if(!dfs(root->left,curmax,curmin))
                return false;
            if(curmax>=root->val)
                return false;
            maxv=max(maxv,curmax);
            minv=min(minv,curmin);
        }
        if(root->right){
            int curmax,curmin;
            if(!dfs(root->right,curmax,curmin))
                return false;
            if(curmin<=root->val)
                return false;
            maxv=max(maxv,curmax);
            minv=min(minv,curmin);
        }
        return true;
    }
};

9.给定一棵二叉树,仅包含 0-9,每条从根节点到叶节点的路径表示一个整数。
例如,如果根节点到叶节点的路径是1->2->3,表示的数就是123。
请计算所有从根节点到叶节点的路径表示的数的和。
在这里插入图片描述
从根节点递归遍历整棵树,遍历时维护从根节点到该节点的路径表示的数,当遍历到叶节点时,将路径表示的数累加到答案中。

时间复杂度分析:每个节点仅被遍历一遍,所以时间复杂度是 O(n)。

class Solution {
public:
    int res=0;
    
    int sumNumbers(TreeNode *root) {
        dfs(root,0);
        return res;
    }
    
    void dfs(TreeNode* root,int s){
        if(!root) return ;
        s=10*s+root->val;
        if(!root->left&&!root->right)res+=s;
        if(root->left)dfs(root->left,s);
        if(root->right)dfs(root->right,s);
    }
};

10.1 给定一个二叉树和一个值sum,判断是否有从根节点到叶子节点的节点值之和等于sum的路径,

class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if(!root) return false;
        if(!root->left&&!root->right) return sum==root->val;
        if(root->left&&hasPathSum(root->left,sum-root->val))return true;
        if(root->right&&hasPathSum(root->right,sum-root->val))return true;
        return false;
    }
};

10.2 给定一个二叉树和一个值sum,请找出所有的根节点到叶子节点的节点值之和等于sum的路径

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    
    vector<vector<int> > pathSum(TreeNode *root, int sum) {
        if(!root) return res;
        dfs(root,sum);
        return res;
    }
    
    void dfs(TreeNode* root ,int sum){
        if(!root) return ;
        path.push_back(root->val);
        sum-=root->val;
        
        if(root->left)dfs(root->left,sum);
        if(root->right)dfs(root->right,sum);
        
        if(!root->left&&!root->right){
            if(!sum)res.push_back(path);
        }
        path.pop_back();
    }
};

10.3 给定一个二叉树,请计算节点值之和最大的路径的节点值之和是多少。
这个路径的开始节点和结束节点可以是二叉树中的任意节点

class Solution {
public:
    int res=INT_MIN;
    int maxPathSum(TreeNode *root) {
        dfs(root);
        return res;
    }
    
    int dfs(TreeNode* root){
        if(!root) return 0;
        int left=max(0,dfs(root->left));
        int right=max(0,dfs(root->right));
        res=max(res,left+root->val+right);
        return root->val+max(left,right);//回溯
    }
};

11.1 给定一课二叉查找树 (BST),找到其中指定两个点的最近公共祖先 (LCA)。
根据 Wikipedia 中 LCA的定义 :“最近公共祖先定义为两个结点 p 和 q 之间,树中最低的结点同时拥有 p 和 q 作为后代(这里允许一个结点的后代为它本身)。
在这里插入图片描述

由于这是一棵二叉查找树,我们可以利用二叉查找树的性质来从根结点开始寻找。

  • 首先根结点必定是候选公共祖先,接着如果 p 和 q 同时出现在左子树,则我们往左儿子移动;
  • 如果 p 和 q 同时出现在右子树,则我们往右儿子移动;
  • 若发现不满足 2 中的两个条件,则停止寻找,当前结点就是最近公共祖先。

时间复杂度
每次都会降低一层,故最坏时间复杂度也就是树的高度 O(h)。

/**
 * 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:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode *cur = root;
        while (1) {
            if (p -> val < cur -> val && q -> val < cur -> val)
                cur = cur -> left;
            else if (p -> val > cur -> val && q -> val > cur -> val)
                cur = cur -> right;
            else
                break;
        }
        return cur;
    }
};

11.2 给定一课二叉树,找到其中指定两个点的最近公共祖先 (LCA)。

(DFS) O(n)
此题与 上一题最大的不同就是这道题给定的二叉树不再是二叉查找树。
所以我们需要通过遍历整棵树,分别找到 p 和 q 结点到根结点的路径,然后枚举匹配路径上的点找到最近公共祖先。

  • 遍历的算法采用的深度优先搜索,搜索时,需要一个数组记录路径;
  • 在每一层尝试两个方向的路径,如果某一个方向找到了目标结点或者当前点就是目标结点,则当且结点加入数组,并返回 true;
  • 否则返回 false。

最后根据两个数组的路径,找到最低的公共结点即可。

class Solution {
public:

    bool dfs(TreeNode *cur, TreeNode *des, vector<TreeNode*> &path_node) {
        if (cur == NULL)
            return false;
        if (cur == des) {
            path_node.push_back(cur);
            return true;
        }
        if (dfs(cur -> left, des, path_node) || dfs(cur -> right, des, path_node)) {
            path_node.push_back(cur);
            return true;
        }

        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path_node_p, path_node_q;
        dfs(root, p, path_node_p);
        dfs(root, q, path_node_q);
        reverse(path_node_p.begin(), path_node_p.end());
        reverse(path_node_q.begin(), path_node_q.end());

        int n = min(path_node_p.size(), path_node_q.size());
        for (int i = n - 1; i >= 0; i--)
            if (path_node_p[i] == path_node_q[i])
                return path_node_p[i];

        return NULL;
    }
};

在这里插入图片描述
方法二:

class Solution {
public:
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		if (root==NULL || root==p ||root==q) //出现了要找的节点,直接返回
		{
			return root;
		}
		//后序遍历,因为上面找到后直接返回,所以实际上不是真正的后序遍历
		TreeNode* left=lowestCommonAncestor(root->left, p, q);
		TreeNode* right=lowestCommonAncestor(root->right, p, q);
		if (left==NULL)//一边是空那么直接返回另一边,因为不管另一边是空或者有找到节点都不会造成影响
		{
			return right;
		}
		else if(right==NULL)
		{
			return left;
		}
		else//左右都非空,那么说明此时的节点是最近的祖先节点
		{
			return root;
		}
	}
};

12.1 序列化是将一个数据结构或对象转换成二进制序列使其能够存储在文件或内存缓冲区,或者通过网络链接传递。之后可以在相同或另一个计算机环境中重新构造该对象。

设计一个算法实现序列化和反序列化一个二叉树,序列化和反序列化的算法没有限制,只需要保证可以将二叉树序列化为字符串,然后该字符串可以被反序列化为原始的树结构。
注意
不得使用类成员、全局或者静态变量存储状态,序列化和反序列化算法不能和状态有关。
在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:
    void solve(TreeNode* root, string& s) {
        if (root == NULL) {
            s += ",";
            return;
        }
        s += to_string(root -> val) + ",";
        solve(root -> left, s);
        solve(root -> right, s);
    }
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string s = "";
        solve(root, s);
        return s;
    }

    TreeNode* decode(int &cur, const string& data) {
        if (data[cur] == ',') {
            cur++;
            return NULL;
        }
        int nxt = data.find(',', cur);//查找从cur下标之后首次出现,的位置
        int val = stoi(data.substr(cur, nxt - cur));
        TreeNode *r = new TreeNode(val);
        cur = nxt + 1;
        r -> left = decode(cur, data);
        r -> right = decode(cur, data);
        return r;
    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        int cur = 0;
        return decode(cur, data);
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

13 (Binary Tree Paths) 给定一个二叉树,返回所有从根结点到叶子结点的路径。

在这里插入图片描述

class Solution {
public:
    vector<string> res;
    string path;
    
    void dfs(TreeNode* root,string path,vector<string> &res){
        
        if(!root->left&&!root->right){
            res.push_back(path);
            return;
        }    
        
        if(root->left) 
            dfs(root->left,path+"->"+to_string(root->left->val),res);
        if(root->right)
            dfs(root->right,path+"->"+to_string(root->right->val),res);
        
    }
    
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if(!root) return res;
        //注意这里根节点先加进去,这样之后每经过一个点就加上“->+val”
        string path=to_string(root->val);
        dfs(root,path,res);
        return res;
    }
};

14 (Verify Preorder Serialization of a Binary Tree) 验证二叉树的前序序列化
在这里插入图片描述
我们可以定义一个概念,叫做槽位,二叉树中任意一个节点或者空孩子节点都要占据一个槽位。二叉树的建立也伴随着槽位数量的变化。开始时只有一个槽位,如果根节点是空节点,就只消耗掉一个槽位,如果根节点不是空节点,除了消耗一个槽位,还要为孩子节点增加两个新的槽位。之后的节点也是同理。

  • 初始化可用槽位:slots = 1。
  • 根据逗号分隔前序序列化,将结果数组存储,随后遍历该数组:

空节点和非空节点都消耗一个槽位:slots = slot - 1.

如果当前的可用槽位是负数,那么这个前序序列化是非法的,返回 False。

非空节点(node != ‘#’)新增两个可用槽位:slots = slots + 2.

  • 如果所有的槽位都消耗完,那么这个前序序列化就是合法的:返回 slots == 0。
class Solution {
public:
    bool isValidSerialization(string preorder) {
        int slot=1;
        
        stringstream s;
        s<<preorder;
        string temp;
        while(getline(s,temp,',')){//以“,”为分割符
            slot--;

            if(slot<0)return false;

            if(temp!="#") slot+=2; 
        }
        return slot==0;
    }
};

附: string用法

  1. 以stringstream类为中间介质,来实现字符串与数字的转化
double a = 133.454;
string str;
stringstream s;
s << a;
s >> str;
  1. 输入n行,每行由不超过规定个数的数字s在一定的范围内,以空格隔开,我们想把起存在一个数组里
string str;
int cnt = 0;
for(int i = 0;i < lines;i++)
{
    getline(cin,str);
    stringstream s;
    s << str; //将字符串输入stringstream的流中
    while(s >> a[cnt]) cnt ++;//将s输入到整型数字中,类比cin,从cin的流中输入到整型数字
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值