LeetCode-树(解题技巧总结三)

 五 递归再理解

894. 所有可能的满二叉树 https://leetcode-cn.com/problems/all-possible-full-binary-trees/

排列组合问题

/**
 * 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 {
    vector<TreeNode*> temp;
public:
    //得到一个列表,存放着所有满足条件的树的root
    vector<TreeNode*> allPossibleFBT(int N) {
        vector<TreeNode*> dp;

        //边界条件1:如果输入的是偶数,return一个空列表
        if(N & 1 == 0) return dp;
        //边界条件2:如果输入为1,那么结果就只有一个值为0的结点
        if(N == 1) {dp.push_back(new TreeNode(0));return dp;}

        //我们知道一共有N个结点,root占了1个结点,左子树可能有1,3,5,..,N-2个结点
        //对应的,右子树可能有N-2,..,5,3,1个结点
        //那么,我们可以用一个循环,找到所有可能的左右子树的可能的数量的情况,把root放进列表里
        for(int i=1;i<=N-2;i+=2){
            //这里就是递归的精髓了,每次看到递归,就一头雾水
            //在这里,我们不用去关心左右子树是怎么递归形成的
            //我们可以仅仅去关心,这个函数,它实现的是什么功能
            //allPossibleFBT(i)返回了一个列表,它存放着当结点数为i时,所有满足条件的树的root的集合
            //我们可以认为,allPossibleFBT(i)存放着所有满足条件的左子树的集合
            //同样,allPossibleFBT(N-1-i)存放着所有满足条件的右子树的集合
            //这是由vector<TreeNode*> allPossibleFBT(int N)这个函数的定义所确定的
            vector<TreeNode*> left = allPossibleFBT(i);
            vector<TreeNode*> right = allPossibleFBT(N-1-i);

            //接下来,就是左右子树的排列组合,当左子树为m时,右子树可能有right.size()个可能
            //所以一共有right.size() * left.size()种可能
            //我们把每一种排列,都放到我们所要的结果中
            for(int j=0;j<left.size();++j){
                for(int k=0;k<right.size();++k){
                    TreeNode *root = new TreeNode(0);
                    root->left = left[j];
                    root->right = right[k];
                    //对于左子树有i个结点,右子树有N-1-i个结点时,我们把所有可能的树push进入队列
                    dp.push_back(root);
                }
            }
        }
        //所以说,看到递归,我们可以屏蔽掉复杂的递归思考过程,而是单纯的把递归函数本身,看成一个封装完全
        //功能独立的一个函数。
        return dp;
    }
};

作者:fuckleetcode
链接:https://leetcode-cn.com/problems/all-possible-full-binary-trees/solution/cban-ben-fu-dai-wan-zheng-de-zhu-shi-by-fuckleetco/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

6序列化

我们在LeetCode-树(解题技巧总结一)中,有部分题目,给了两种遍历方法,要求用户求解树的结构

序列化也是其中的一部分,只不过不需要知道多个遍历结果,知道一个即可。

面试题37. 序列化二叉树 https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/

297. 二叉树的序列化与反序列化 https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/

449. 序列化和反序列化二叉搜索树 https://leetcode-cn.com/problems/serialize-and-deserialize-bst/

将树序列化,然后根据序列化的结果,将其反序列化,变成树

至于序列化的规则,那么就根据用户进行定义了,只要能正常的序列化和反序列化,怎么都可以

首先使用DFS序列化,选择前序遍历

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
    //前序遍历   
    // 1,2,NULL,NULL,3,4,NULL,NULL,5,NULL,NULL
        if(root == nullptr) return "NULL";
        string Root = to_string(root->val);
        string Left = serialize(root->left);
        string Right = serialize(root->right);
        return Root+','+Left+','+Right;
    }

输出结果:

1,2,NULL,NULL,3,4,NULL,NULL,5,NULL,NULL

序列化都不是问题的关键,怎么序列化都好,BFS都可以,问题是如何反序列化。

既然是先序遍历,而且又空,那么就继续使用DFS进行反序列化即可

TreeNode* root = new TreeNode(NumVal);
root->left = Decodes(data, index);
root->right = Decodes(data, index);

碰到叶子结点,返回左右自然都会返回null

根据叶子结点的情况,会自然而然的分成左右子树,只要控制好访问的点,也就是游标Index

下面看反序列化的完整版

    TreeNode* deserialize(string data) {
        cout<<data;
        int Index = 0;
        return Decodes(data,Index);
    }
    TreeNode* Decodes(string data,int &index){
        if(index >= data.size()-1) return nullptr;//说明遍历完成了
        int numEnd = index;
        while(numEnd <= data.size()-1&&data[numEnd]!=',') numEnd++;//把一个数值找全了,因为有可能是多位数字
        //注意此处的if判断,放在什么位置,对程序影响很大,放在while后面,就是End+1,因为此时end是在一个逗号上,放在其他位置一定要注意
        if(data[index] == 'N') {index = numEnd + 1;return nullptr;}//空,返回空
        int NumSign = 1,NumVal = 0;
        if(index<numEnd&&data[index] == '-') {NumSign = -1;index++;}//符号
        for (int i = index; i < numEnd; i ++ ) NumVal = NumVal * 10 + data[i] - '0';//字符串变成数字
        NumVal *= NumSign;//确定符号
        index = numEnd + 1;
 
        TreeNode* root = new TreeNode(NumVal);
        root->left = Decodes(data, index);
        root->right = Decodes(data, index);
        return root;
    }

递归难在哪儿?难在目前要到底是要访问字符串的那个位置,这个是难点,所以必须是引用传递值

关于N,空值的判断也是需要尤为的小心谨慎的。

 

注意程序中的各处细节,都非常值得推敲,尤其是不借助任何函数和接口,将字符串变成数字。

完整版本(优化版)

class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == nullptr) return "N";
        string Res = to_string(root->val) + ',' + serialize(root->left) + ',' + serialize(root->right);//DFS最好想象
        return Res;
    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        int begin = 0;
        return DFS(data,begin);
    }
    TreeNode* DFS(string data,int & begin){
        if(begin>=data.size()) return nullptr;//停止递归
        int end = begin;
        while(end<data.size()&&data[end]!=',') end++;
        string num = data.substr(begin,end - begin);
        begin = end+1;
        if(num == "N") return nullptr;

        int Value = StringToInt(num);
        TreeNode* root = new TreeNode(Value);
        root->left = DFS(data,begin);
        root->right = DFS(data,begin);
        return root;
    }
    int StringToInt(string num){
        int Res = 0,flag = 1,index  = 0;
        if(num[index] == '-') {index++;flag = -1;}
        for(;index<num.size();index++) Res = Res*10 + num[index] - '0';
        return Res*flag;
    }
};

使用BFS进行编码:

  1. 编码细节:对于NuLL的处理
  2. 分割字符
  3. 字符变数字(保留符合)
  4. 如何使用BFS重构树
/**
 * 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:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {//按照BFS的方式编码
        if(root == nullptr) return "";
        string Temp;
        queue<TreeNode*>Q;
        Q.push(root);
        int number = 0;
        while(Q.size())
        {
            int size = Q.size();
            for(int i = 0 ;i<size;++i)
            {
                TreeNode* temp = Q.front();Q.pop();  
                if(!temp) 
                {
                    Temp += "N,";break;
                }
                Q.push(temp->left);
                Q.push(temp->right);
                Temp += to_string(temp->val)+',';
            }
        }
        return Temp;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {//按照BFS的方式解码
        if(data == "")  return nullptr;
        cout<<data<<endl;
        string Temp;
        queue<string> Q;
        int begin = 0,len = 0,index = 0;
        while(begin<data.size())//剔除多余部分,Q中的各个内容都是每个结点的值 
        {
            
            while(data[index]!=',') {len++;index++;}
            Q.push(data.substr(begin,len)); 
            begin += len+1;
            index = begin;
            len = 0;
        }
        queue<TreeNode*> R;
        //处理根结点
        TreeNode * root = new TreeNode( Jadgenum(Q.front()) );Q.pop();
        R.push(root);
        while(Q.size())
        {
            // cout<<Q.front()<<endl;
            // Q.pop();
            TreeNode * Lroot = new TreeNode();
            if(Q.front()!= "N")
            {
                Lroot->val = ( Jadgenum(Q.front()) );
                Q.pop();
                // cout<<Lroot->val<<endl;
                R.push(Lroot);
            }
            else {Q.pop();Lroot = nullptr;}
            TreeNode * Rroot = new TreeNode();
            if(Q.front()!= "N")
            {
                Rroot->val = ( Jadgenum(Q.front()) );
                Q.pop();
                // cout<<Rroot->val<<endl;
                R.push(Rroot);
            }
            else {Q.pop();Rroot = nullptr;}//不管是不是零,都要弹出

            TreeNode * Troot = R.front();R.pop();
            if(Troot){Troot->right = Rroot;
            Troot->left = Lroot;}
        }
        // cout<<Temp<<endl;
        return root;
        
    }
    int Jadgenum(string data)//字符串变成数字
    {
        int NumSign = 1,NumVal = 0,index = 0,numEnd = data.size();
        if(index<numEnd&&data[index] == '-') {NumSign = -1;index++;}//符号
        for (int i = index; i < numEnd; i ++ ) NumVal = NumVal * 10 + data[i] - '0';//字符串变
        return NumSign*NumVal;
    }
};

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

 

本题还有一个部分值得推敲:带符号字符串转化为数字

    int Jadgenum(string data)//字符串变成数字
    {
        int NumSign = 1,NumVal = 0,index = 0,numEnd = data.size();
        if(index<numEnd&&data[index] == '-') {NumSign = -1;index++;}//符号
        for (int i = index; i < numEnd; i ++ ) NumVal = NumVal * 10 + data[i] - '0';//字符串变
        return NumSign*NumVal;
    }

注意:此处是等于,不是+=,注意细节。 

字符串解包,将一个字符串解包,各个数字的字符串形式保存在队列里面

        while(begin<data.size())//剔除多余部分,Q中的各个内容都是每个结点的值 
        {
            
            while(data[index]!=',') {len++;index++;}
            Q.push(data.substr(begin,len)); 
            begin += len+1;
            index = begin;
            len = 0;
        }

以上都是准备工作,那么对于BFS或者DFS来说,重要是创建新的数组

DFS倒是好办,但是对于BFS,问题就来了,层序遍历怎么办?

我们先创建根结点,然后创建左右子树,让他们和根结点先连接,然后再把左右子树压入队列,为了下一行做准备

//处理根结点
        TreeNode * root = new TreeNode( Jadgenum(Q.front()) );Q.pop();
        R.push(root);
        while(Q.size())
        {
            // cout<<Q.front()<<endl;
            // Q.pop();
            TreeNode * Lroot = new TreeNode();
            if(Q.front()!= "N")
            {
                Lroot->val = ( Jadgenum(Q.front()) );
                Q.pop();
                // cout<<Lroot->val<<endl;
                R.push(Lroot);
            }
            else {Q.pop();Lroot = nullptr;}
            TreeNode * Rroot = new TreeNode();
            if(Q.front()!= "N")
            {
                Rroot->val = ( Jadgenum(Q.front()) );
                Q.pop();
                // cout<<Rroot->val<<endl;
                R.push(Rroot);
            }
            else {Q.pop();Rroot = nullptr;}//不管是不是零,都要弹出

            TreeNode * Troot = R.front();R.pop();
            if(Troot){Troot->right = Rroot;
            Troot->left = Lroot;}
        }
        // cout<<Temp<<endl;
        return root;

序列化是一道非常考验编程基本的题目,需要耐心与细心,缜密的逻辑,清晰的思路,是一道非常好的题目。 

BFS优化版本如下:

/**
 * 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:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == nullptr) return "";
        string Temp;
        DFS(root,Temp);
        // cout<<Temp<<endl;
        return Temp;
        
    }
    void DFS(TreeNode* root,string& Temp)
    {
        if(!root) {Temp += "N,";return;}
        Temp += to_string(root->val)+",";
        DFS(root->left,Temp);
        DFS(root->right,Temp);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()) return nullptr;
        queue<string> SQ;
        CutString(data,SQ);
        return CodeDFS(SQ);
        return nullptr;
        
    }
    TreeNode* CodeDFS(queue<string>& SQ)
    {
        if(SQ.size() == 0) return nullptr;
        if(SQ.front() == "N") {SQ.pop();return nullptr;}
        int val = DecodeNumber(SQ.front());SQ.pop();
        TreeNode* root = new TreeNode(val);
        root->left = CodeDFS(SQ);
        root->right = CodeDFS(SQ);
        return root;
    }
    int DecodeNumber(string& data)
    {
        if(data == "") return 0;
        int index = 0;
        int Sign = 1,Number = 0;
        if(index<data.size()&&data[index] == '-') {Sign = -1;index++;}
        for(;index<data.size();index++) Number = Number*10 + data[index] - '0';
        return Sign*Number;
    }
    void CutString(string data,queue<string>& SQ)
    {
        if(data.empty()) return;
        int index = 0,begin = 0,len = 0;
        while(begin<data.size())
        {
            while(data[index]!=',') {index++;len++;}
            SQ.push(data.substr(begin,len));
            begin += len+1; 
            len = 0;
            index = begin;
        }
    }
};

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

 

序列化的应用:

652. 寻找重复的子树 https://leetcode-cn.com/problems/find-duplicate-subtrees/

 

此题,最好的办法,就是Hash表和序列化

不论怎么遍历,每次都将序列化的内容放入表中,在表中一旦找到重复的,就标记

class Solution {
public:
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        vector<TreeNode*> res;
        unordered_map<string, int> mp;
        dfs(root, res, mp);
        return res;
    }
    
    string dfs(TreeNode* root, vector<TreeNode*>& res, unordered_map<string, int>& mp){
        if(root==0) return "";
        //二叉树先序序列化
        string str = to_string(root->val) + "," + dfs(root->left, res, mp) + "," + dfs(root->right, res, mp);
        
        if(mp[str]==1){
            res.push_back(root);
        } 
        mp[str]++;
        return str;
    }
};

就是在序列化的基础上增加了hash表的部分,显然可以看到,在mp[str] == 1的时候,然后找到了重复的。

因为只有重复的,val数值是1。当然是用find函数也可以。

本题的核心就是序列化和到底是如何序列化

        string str = to_string(root->val) + "," + dfs(root->left, res, mp) + "," + dfs(root->right, res, mp);
        
        if(mp[str]==1){
            res.push_back(root);
        } 

递归的位置,Hash表检定的位置,都是本题的核心关键。

回看本题,序列化还是很好想到的,Hash表的设置是关键,我们还需要在Hash表的增加过程中完成剪枝功能

class Solution {
public:
    vector<TreeNode*> Res;
    unordered_map<string,TreeNode*>M;
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        if(root == nullptr) return {};
        DFS(root);
        return Res;
    }
    string DFS(TreeNode* root)
    {
        if(root == nullptr) return "N";
        string temp = to_string(root->val) + ',' + DFS(root->left) + ',' + DFS(root->right);
        if(M.find(temp) != M.end()) Res.push_back(M[temp]);
        else M[temp] = root;
        return temp;
    }
};

如果我们将Hash表设置为如上的情况,输出:

显然无法完成剪枝,我们需要记录的是相同的结构,出现的次数,每次出现一次就记录,当为1的时候才记录结果

下面的代码非常关键:这个细节如果没有想到,我们可能无法完成剪枝。

        if(mp[str]==1){
            res.push_back(root);
        } 
        mp[str]++;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值