五 递归再理解
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进行编码:
- 编码细节:对于NuLL的处理
- 分割字符
- 字符变数字(保留符合)
- 如何使用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]++;